LaravelでGoogleSheetAPIを叩き、本番環境で詰みまくった件

## やりたいこと

Google Sheet APIを通して、Laravelからスプレットシートの情報を取得し、表示させる!!

## なぜやりたいか？

今回は100以上の項目のある測定テストを作成して、回答者が自分が回答した問題をRadar Chartで見せたい。ユーザー側は自分の情報をいつでも見れるように、`Laravel Breeze`を使って、ログインし、自分が回答した結果を見れる仕組みにしたい。

作りたい仕組みはこちら：

![スクリーンショット 2023-06-05 23.56.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959760/caa77aba-6d8c-8a31-04d7-3dd206a454ac.png)

## つまり、LaravelでAPIを扱うことに

LaravelのView画面からJavaScriptでAPIを叩いたことありますが、今回はコントローラからAPI情報を入れてみました。

理由はGoogleスプレットシートの共有部分に、共有するメールアドレスが必要(後ほど説明！）のと、JavaScript以外のやり方にチャレンジしたかったです。

#### やってみたこと：

① Google Cloud PlatformでSheetAPIを有効にし、サービスキーを作成する
② Laravelのコントローラから認証情報を取得
③ Chart.jsでデータを処理して反映する

③はChart.jsを調べればすぐできますので、今回は認証情報とLaravelで苦戦したところを共有します。

今回使ったChart.jsのサンプルコードはこちら

https://www.chartjs.org/docs/latest/samples/other-charts/radar.html

## 苦戦した部分

苦戦の連続でした。主に情報取得と引き渡し、そして本番環境でめちゃくちゃ詰んだ。

#### 苦戦①：どのキーを使えば情報を取得できるの？

SheetAPIを動かしたいので、どんな認証方法がいいの？APIキーでもいいの？
正直、イマイチ理解してなかった。GCPを開いたら、3つもある！！

![スクリーンショット 2023-06-06 0.29.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959760/91922839-9bcb-fa19-c58a-0e441e0ebd88.png)

1. API キー
2. OAuth 2.0 クライアント ID
3. サービス アカウント


[公式][link-1]から、細かく説明があります。 

[link-1]: https://cloud.google.com/docs/authentication?hl=ja&_ga=2.30143743.-1597498805.1677388188&_gac=1.23670088.1685773940.CjwKCAjwpuajBhBpEiwA_ZtfhegVMrLhn-fyCYkD-KY-hHle4hSbHQDcxR4qigKqmVTd_zlagyphQRoCKgMQAvD_BwE

ざっくりと言いますと、今回はスプレットシートの情報を取得したいので、スプレットシートの権限をもらう必要があります。だから、スプレットシートから共有が必要ですので、サービスアカウントキーが最も簡単で便利です。（セキュリティも悪くないようです。）

補足：　なぜAPIキーはダメでしたか？
公式ドキュメンテーションによると、多くのAPIがAPIキーに対応しないそうです。つまり、コードを書けたら、OAuthがいい、スプレットシートくらいなら、サービスアカウントだったら手っ取り早い!!

手順：

① もらったサービスアカウントの情報を入力
![スクリーンショット 2023-06-06 0.40.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959760/b50568cf-9b17-bfb6-87c9-a6c43b509e1a.png)

② サービスキーのJSONファイルをダウンロード
詳しい設定はこちらの記事を参考しました。設定時ダウンロードしないと、２度と取得できないようです。

https://clo-support.flight.co.jp/hc/ja/articles/14858881486873-Google-API-%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E4%BD%9C%E6%88%90-%E8%A8%AD%E5%AE%9A%E6%89%8B%E9%A0%86

③ サービスキーファイルをStorageに保存

※ここは結構大事です!! めちゃくちゃ詰んだ部分です。

いろいろ試したあと、`Storage/App/private/`の下で配置するようにしました。理由はセキュリティ上、アクセスしにくい場所がいい。そして、.envファイルのように、本番環境で多量の手打ちすることもない。


#### 苦戦②：情報をどう取得すればいいの？

認証取得だけで、二日ほど苦戦しました。見飽きたエラーはこちらです。

`データの取得に失敗しました SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON`

データ取得できない時によく出るSyntaxエラーらしい。このエラーが登場する時、考えられる可能性は、

1. 認証情報が間違ってる。
2. 設定ミス
3. サービスキーのファイルはあっていますが、パスが通ってない。

実際コントローラで書いたコードはこんな感じです。

```
$client = new Google_Client();
        $key_file = storage_path('app/private/credential.json'); 
        $client->setAuthConfig($key_file);
        $client->setScopes([
            'https://www.googleapis.com/auth/spreadsheets'
        ]);
```

先ほど話したサービスキーファイルを storage/app/private/credential.json
で保存します。パスの通し方はこんな感じ：`storage_path('app/private/credential.json');`

あとはわりとシンプルです。スプレットシートの情報を取得し、response経由でviewの方に引き渡すだけです。

```
        $sheetId = '1ml44HFe1tGhl7_eJgIbB4EQjDusAami3gRUTA64zdps';
        
        $sheetService = new Google_Service_Sheets($client);
        $range = 'Sheet1'; // Sheet1の内容全てを取得
   
        
        $response = $sheetService->spreadsheets_values->get($sheetId, $range);
        $values = $response->getValues();//データを配列で取得
    

        // return response()->json($values);// 取得したデータをチェックするときはこちらを
        return response()->view('googleSheet',compact('values'));//viewの方に取得したデータを引き渡し
```

#### 苦戦③：本番環境で詰んだ

今回はさくらサーバーでデプロイしました。デプロイした瞬間、動いてたChart.jsが動かなくなりました。なぜだ!!!!!

可能性をいろいろ模索しました。やってみてもうまくいかなかったこと：

・.envファイルでのphpMyAdmin設定
・google clould jsonキーは本番環境に入れる

そして、同期からもらったアドバイス：

・セキュリティー関連のパーミッション問題
・ローカルと本番のギャップによるマシン問題
・そもそも認証が間違ってる
などなど

ほぼ総当てして、最終的に動かせた。
正直どれか正解なのかわかりませんが、最後に試したことはこれです。

```
fetch("{{ route('sheet') }}?email=" + email,{
            //本番環境入ってから追加項目
            headers : { 
            'Content-Type': 'application/json',
            'Accept': 'application/json'
            }
           })
```
`'Content-Type': 'application/json'`を明記することによって、引き渡されたファイルはちゃんとJSONファイルですよ〜ということらしいです。

本番環境はローカル環境より厳格ということかな？
viewのコードはこちら：
```
<body>
//認証機能
    @auth
    <div class="flex justify-center">
    <input type="hidden" id="email" name="email" value="{{ auth()->user()->email }}">
   <x-primary-button id="fetchData" class="mt-4 mb-4 py-2 px-4 bg-blue-500 text-black rounded-lg">結果表示</x-primary-button>

    </div>
     @endauth
       
//canvasを使ってChart.jsを表示・描画
    <canvas id="radarChart"></canvas>

    <script>
        // データ取得用のAjaxリクエストを送信する関数
        function fetchData() {
            const email = document.getElementById('email').value;
            // Ajaxリクエストを送信
            fetch("{{ route('sheet') }}?email=" + email,{
            
            headers : { 
            'Content-Type': 'application/json',
            'Accept': 'application/json'
            }
           })
           .then(response => response.json())
                
                .then(data => {
                    // データを取得してレーダーチャートを描画する関数を呼び出す
                    drawRadarChart(data);
                })
                  .catch(error => {
                     console.error("データの取得に失敗しました", error);
                 });
              
        }
       
        // レーダーチャートを描画する関数
        function drawRadarChart(data) {
            
         //引き渡されたデータの処理は割愛。こっからはChart.jsのサンプルコードを参照

            const canvas = document.getElementById('radarChart');

            new Chart
            (canvas, {
                type: 'radar',
                data: {
                    labels: [
                    'label1', 
                    'label2', 
                    'label3',  
                    'label4',  
                    'label5',  
                    'label6',  
                    'label7', 
                    ],
                    datasets: [{
                        label: `${timestamp}`,
                        data: [Q1result,Q2result,Q3result,Q4result,Q5result,Q6result,Q7result],
                        fill: true,
                        backgroundColor: 'rgba(75, 192, 192, 0.2)',
                        borderColor: 'rgba(75, 192, 192, 1)',
                        borderWidth: 1
                    }]
                },
                options: {
                    scale: {
                        ticks: {
                            beginAtZero: true,
                            max: 100
                        }
                    }
                }
            });
        }
        

        // データ取得ボタンのクリックイベントを設定
        document.getElementById('fetchData').addEventListener('click', fetchData);
    </script>
</body>
```
引き渡されたデータをJavaScriptで処理して、完成!!

## 感想

本番環境でもチャートが表示されました!!! 歓喜!!!!! 

![スクリーンショット 2023-06-06 1.36.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2959760/21bd75fd-7f0d-f489-e6c9-4d2d26e932de.png)

いろんな人の協力を得て、なんとかできました。これからは複数のデータを重ねていく仕組みを考えます。
引き続き、GoogleAPIサービスを使っていきたいと思います^^

## 最後に、、、最近の私。

携わっているByNameがリリースして、私のプロフィールもぜひみてもらいたいと思ってます！！

https://byn.jp/flyer/ebfca40c-d718-4d02-8a14-dfd93cb3d716