GASで実現！スプレッドシートからGCSへファイルをアップロード

## はじめに
弊社（VENECT株式会社）では、毎年SNSやECに関するアンケートを実施し、その集計結果をもとにスライド資料をPowerPointで作成してきました。今年度、そのCSVファイル形式のアンケートデータをLooker Studioでグラフ化するプロジェクトをインターンメンバーで実施しましたので、3回の記事に分けてトピックを紹介します。

以前の記事でLooker Studioでのアンケートデータ可視化手法の概要について紹介しました。その中でGoogle Cloud Storage（GCS）上にファイルをアップロードする必要があり、スプレッドシート上からGASでアップロードするプログラムを作成したので、今回はその紹介をさせて頂きます。

### 関連記事
https://qiita.com/venect_qiita/items/1e4e30cd50d34c64cdfe

https://qiita.com/venect_qiita/items/0b26e0052fd7a5f67323

## 概要
この記事では、Google Apps Script (GAS) を用いてHTMLフォームを介し、Google Cloud Storage (GCS) にファイルをアップロードする手順を紹介します。具体的なコード例や設定方法を交えながら、実装の流れを丁寧に説明していきます。

### イメージ
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/83a1c132-b9bc-8838-95b5-cce9b77e29c3.png
width="80%">

## GASとは
__Google Apps Script (GAS)__ は、Googleが提供するクラウドベースのスクリプト環境で、Googleドライブ上にあるドキュメント、スプレッドシート、Gmailなどの処理を自動化したり、簡単なWebアプリケーションなどを開発できます。GASはJavaScriptベースで動作し、Googleのサービスと連携して、以下のような作業を効率化することができます。


### GASでできること
* データの自動処理：スプレッドシートや他のGoogleサービス上のデータをスケジュールに従って更新、管理
* Googleサービスの連携：スプレッドシートの内容をもとにメールを自動送信したり、カレンダーにイベントを追加したりする
* カスタムアプリの作成：GASを使って独自のWebアプリや、スプレッドシートのカスタムメニューを作成可能

GASの強みは、これらの操作がGoogleサービス内で統一されているため、簡単に連携や自動化が行える点です。今回解説するGoogle Cloud Storage (GCS) へのファイルアップロードは、GASを使えば簡単に実装できます。GCSはGoogle Cloudのサービスで、GASと連携することでGoogle Workspace のアプリから手軽に利用できます。

## GCSとは
__Google Cloud Storage (GCS)__ は、Googleが提供するクラウドストレージサービスで、大容量のファイルを安全に保存し、世界中のどこからでもアクセスできる環境を提供します。GCSは、Google Cloudの一部として位置づけられ、多様な用途に対応したデータの保管や管理が可能です。

### GCSでできること
* 大容量ファイルの保存：テキストファイルや画像、動画など、さまざまな形式のファイルを大量に保存可能
* データの一元管理：複数のプロジェクト間で共有したり、いつでもどこからでもアクセス可能なクラウドストレージを提供
* 高度なセキュリティ管理：アクセス権限や暗号化機能を通じて、保存したデータのセキュリティを確保

今回のように、GASを使ってHTMLフォームからファイルをGCSにアップロードすることで、手動でのファイル管理が不要になり、効率化が図れます。

## 事前準備

### 必要なもの
* Google Cloudのサービスアカウント

今回はサービスアカウントを使用する前提です。サービスアカウントを設定することで、GCSへのアクセスに必要なトークンをリクエストし、APIを通じて安全にGCSと連携することができます。

サービスアカウントの作成に関しては以下の記事を参考にしてください

https://qiita.com/Nekoq0o0p/items/71d962ed9a93b95df5bf

### Google Cloud Strage (GCS) のセットアップ
1. Google Cloudにアクセス
Google Cloudの[コンソール](https://console.cloud.google.com/?hl=ja)にログインします。

2. バケットの作成
GCSにファイルを保存するために、バケットを新しく作成します。バケットはGCS内でファイルを保存するためのフォルダのようなものです。
左上のメニュー選択ボタンを押して __Cloud Storage__ -> __Buckets__ -> __CREATE__ の順にクリックします。その後、必要に応じてバケットの設定を行います。
<div style="display: flex; gap: 10px;">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/7f66f6ef-6cc5-cd45-385a-512013e67f7d.png" width="47%">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/c11c9568-b848-9561-8880-43a4d7f22707.png" width="38%">
</div>

バケットの詳細設定に関しては、以下の公式ドキュメントが参考になります。

https://cloud.google.com/storage/docs/discover-object-storage-console

## GASの実装
作成するファイルは以下の3つです。
* main.gs
* file_upload.html
* gcs.gs

また、サービスアカウントの情報はGASのスクリプトプロパティに以下の名前で登録してください。

<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/94b6e537-39e5-ffac-fb0a-563177bd0adb.png
width="75%">

### main.gs
操作メニューの表示とファイルのアップロードを行うための関数が実装されています。
```javascript:main.gs
function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('操作メニュー')
    .addItem('ファイルアップロード', 'showForm')
    .addToUi();
}

function showForm(){
  const fileName = 'file_upload';
  const title = 'アンケートデータのアップロード';
  const html = HtmlService.createTemplateFromFile(fileName).evaluate();
  SpreadsheetApp.getUi().showModalDialog(html, title);
}

function uploadFile(form) {
  const token = getIdToken();
  const file = form.uploadFile;
  const bucketName = 'insert_spreadsheet'; //GCSのバケット名
  const folder = 'qiita_test'
  const contentType = file.getContentType();
  const content = file.getBytes();

  const url = `https://storage.googleapis.com/${bucketName}/${folder}/${file.getName()}`
  try {
    UrlFetchApp.fetch(url, {
      headers: {
        Authorization: "Bearer " + token,
      },
      method: "PUT",
      contentType: contentType,
      payload: content
    });
  } catch (e) {
    SpreadsheetApp.getUi().alert('アップロード中にエラーが発生しました: ' + e);
  }

  return 'close';
}
```

- onOpen関数
スプレッドシートを開いたときにカスタムメニュー「操作メニュー」を作成し、その中に「ファイルアップロード」というメニュー項目を追加します。この項目をクリックすると、showForm 関数が呼び出されます。
<img src=https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/ef7b18d9-50be-efe5-7517-2c6ed8a6a76b.png
width="60%">

&#13;&#10;

- showForm関数
file_upload というHTMLテンプレートを読み込み、「アンケートデータのアップロード」というタイトルでダイアログを表示します。これにより、ユーザーがスプレッドシート上でファイルをアップロードするためのフォームが表示されます。

&#13;&#10;

- uploadfile関数
ユーザーがフォームを通じて選択したファイルを、GCSにアップロードします。
認証トークンとファイル情報を取得し、その情報を使用して指定のパスへHTTP PUTリクエストを送信します。エラーが発生した場合は、スプレッドシートのUIにアラートを表示してユーザーに通知します。

### file_upload.html
ファイルをアップロードするためのフォームです。
```html:file_upload.html
<!DOCTYPE html>
<html>
  <body>
    <form onsubmit='return false;'>
      <input type='file' name='uploadFile' />
      <input type='button' value='アップロードする' onclick='clickSubmit(this.form);'>
    </form>
    <script>
      function clickSubmit(form) {
        alert("アップロード中です...");

        google.script.run
          .withSuccessHandler((result) => {
            if (result === 'close') {
              alert("ファイルのアップロードが完了しました。");
              google.script.host.close();
            }
          })
          .withFailureHandler((e) => {
            alert("エラーが発生しました: " + e);
          })
          .uploadFile(form);
      }
    </script>
  </body>
</html>
```
ファイルを選択して「アップロードする」ボタンを押すと、Google Apps Script が呼び出され、ファイルが GCS にアップロードされます。アップロードが成功すると完了メッセージが表示され、失敗した場合はエラーの原因が表示されます。


<div style="display: flex; gap: 10px;">
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/1d5d6c1d-4719-345e-fd17-969f38e473ab.png" width="39%">
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/ec86ee98-3ee0-6281-9ed2-48ec7b6f0c56.png" width="59%">
</div>


### gcs.gs
GCSにアクセスするためのトークン取得するための関数が実装されています
```javascript:gcs.gs
function getIdToken() {
    const options = {
        'method': 'post',
        'payload': {
            'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion': getAssertion()
        }
    };
    const responseContent = UrlFetchApp.fetch('https://oauth2.googleapis.com/token', options).getContentText();
    const response = JSON.parse(responseContent);
    return response.access_token;
}


function getAssertion() {
    const privateKey = PropertiesService.getScriptProperties().getProperty('PRIVATE_KEY').replace(/\\n/g, '\n');
    const header = {
        alg: 'RS256',
        typ: 'JWT'
    };
    const now = new Date();
    const claimSet = {
        iss: PropertiesService.getScriptProperties().getProperty('CLIENT_EMAIL'),
        sub: PropertiesService.getScriptProperties().getProperty('CLIENT_EMAIL'),
        aud: 'https://oauth2.googleapis.com/token',
        exp: Math.floor((now.getTime() + 3600000) / 1000),
        iat: Math.floor(now.getTime() / 1000),
        scope: 'https://www.googleapis.com/auth/devstorage.read_write' //変更点 
    };
    const toSign = Utilities.base64EncodeWebSafe(JSON.stringify(header)) + '.' + Utilities.base64EncodeWebSafe(JSON.stringify(claimSet));
    const signatureBytes = Utilities.computeRsaSha256Signature(toSign, privateKey);
    const signature = Utilities.base64EncodeWebSafe(signatureBytes);
    return toSign + '.' + signature;
}
```
* getIdToken関数
Google Cloud Storageにアクセスするための認証トークンを取得します。getAssertion関数で生成した認証情報を用いて、Googleの認証サーバーにリクエストを送信し、返ってきたレスポンスからアクセストークンを取得して返します。

&#13;&#10;

* getAssertion関数
Google Cloud Storageへのアクセスに必要なJWT（JSON Web Token）を生成します。まず、秘密鍵を使用して署名を行うための認証情報を作成し、scopeにGCSへのアクセスに必要な範囲を指定します。このJWTを用いてアクセストークンを取得するリクエストが、正しく認証されるように設定されています。
参考記事の中では __target_audience__ が書かれていますが、今回の場合 __scope__ を書いてください。

コードの詳細な説明やトークン取得の概要については、以下の記事の「サンプルコードの詳細」項目を参考にしてください。

https://qiita.com/venect_qiita/items/26634a32037e40a58508#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E8%A9%B3%E7%B4%B0

## データ確認
試しに、a.txtというテキストファイルを指定の場所にアップロードしてみましょう。

### **事前設定** 
- **Bucket**: `insert_spreadsheet`
- **Folder**: `qiita_test `
(今回は事前に作成していません)
- **File**: `a.txt`

> **注意**: `Bucket` は事前に作成しておく必要があります。`Folder` は、既に存在している場合はその中に保存され、存在しない場合は指定した名前で自動的に作成されます。


以下の画像のようにアップロードを行います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/d82a1551-b050-cb56-2c59-7185f91cb989.png" width="80%">

その後、「insert_spreadsheet」バケット内を確認すると、「qiita_test」フォルダが自動生成されていることがわかります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/abc7aadb-0ced-3c28-ba2b-5e6d13859974.png" width="80%">


さらに、「qiita_test」フォルダ内を見てみると、アップロードしたa.txtファイルが存在するのを確認できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2598043/39e44826-66e9-988c-0811-4c7688bf1250.png" width="80%">

このように、指定のフォルダへのアップロードを正しく行うことができました。

## 最後に
今回の記事では、Google Apps Script (GAS) を用いて、スプレットシートからGoogle Cloud Storage (GCS) にファイルをアップロードする方法を解説しました。スプレッドシート内でデータのアップロードができるようになるため、業務の効率化やチームでのファイル共有に役立つかと思います。

今後も、GASやGoogle Cloudを活用した便利なツールの作り方等を紹介していく予定ですので、ぜひ引き続きご覧ください。この記事が皆様のプロジェクトや業務改善の一助となれば幸いです。

最後までお読みいただきありがとうございました！

### 関連記事
https://qiita.com/venect_qiita/items/1e4e30cd50d34c64cdfe

https://qiita.com/venect_qiita/items/0b26e0052fd7a5f67323