【書籍管理シリーズ】GASとFireBase(Firestore)を連携させるよ！

調子乗ってアドカレ登録しまくってしまったのですが、頑張って記事書いていきたいと思います！
せっかくなので複数のアドカレをまたがってシリーズ記事を作成しようかなと。

書籍管理システム作成記録第１弾です。
まずは『GoogleAppsScript』!


【書籍管理シリーズ】
・ 第一話 GASとFireBase(Firestore)を連携させるよ！　※当記事

## 作成したもの
弊社では社員の興味のある技術をベースに技術本の貸し出しを行なっているのですが、新作が入ったら書籍担当が手入力でスプレットシートを更新し、貸し出しの時は本人が貸し出しシート(紙)に名前と貸出日を記入、そして書籍棚を見ないと、今どんな本が会社にあり、借りることができるのかが一切わからないというとても勿体無いかつ不便な状態でした。

これを、本を新しく購入したらスプレットシートのフォームにISBNだけを入力し更新したら、別シートに情報が全て記載。
かつ、FireBaseのFirestoreと連携し、外部からそのデータをみれるようなロジックを作成しました！

今記事ではGoogle Apps Script(スプレットシート)とFireBase(Firestore)の連携部分のみの記載ですが、別記事で含めて一通りの作成記事を連載したいと思います。

<img width="637" alt="スクリーンショット 2018-12-02 02.15.33.png" src="https://qiita-image-store.s3.amazonaws.com/0/203392/f5cb9f1e-a9e9-daf9-337d-332b8fe62439.png">

<img width="956" alt="スクリーンショット 2018-12-02 02.15.57.png" src="https://qiita-image-store.s3.amazonaws.com/0/203392/96fc275a-3d75-625c-663a-075b91f1b667.png">


## 今回Google Apps Scriptを使った理由。
今回に関しては非エンジニアが管理することもあるため、全員が同じように登録かつ見やすい一覧にする必要がありました。
弊社では情報の管理を主にスプレットシートで管理していたため、同じツールを使うことで導入コストを削減しました。


## ISBNで書籍登録をする
まずは書籍登録の自動化部分を作成します！

**管理する上で必要な情報**
・管理スタート日
・タイトル
・著者
・出版社
・出版日
・ISBN
・あらすじ
・表紙の画像

**書籍検索API**
今回は無料の範囲で全て作成したかったのでFireBaseのSpark(無料)プランで作成をするのですが、
最終的にCloud Functionsを使用する関係でGoolgeから提供されているサービスに絞る必要がありました。

というわけで、今回使用するAPIは『[Google Books APIs](https://developers.google.com/books/?hl=ja)』一択です。

Google Books APIsはGoogleブックスの機能をAPIを通して使用する事ができるサービスです。
今回は書籍検索にしか使用しませんが、いずれ他の機能も使いこなしたいです。

ISBNで書籍情報を取得したい時は
`https://www.googleapis.com/books/v1/volumes?q=isbn:' + ISBN番号`を叩くと書籍に関する情報が返って来ます。

試しに`https://www.googleapis.com/books/v1/volumes?q=isbn:9784798053769`を開いて見て下さい。
『[詳解! GoogleAppsScript完全入門](https://books.google.co.jp/books?id=OUlNDwAAQBAJ&dq=Google+Apps+Script&hl=ja&source=gbs_navlinks_s)』の書籍詳細のjsonデータが表示されたでしょうか？

今回実装した書籍登録用コードはこちら↓

```javascript
// ※数字部分はよしなに自分の環境に合わせて差し替えて下さい

var ss = SpreadsheetApp.getActiveSpreadsheet();
// ツールバーにカスタムメニューを追加
function onOpen(){
  var menu = [
    {name: '書籍登録', functionName: 'setDataRegistration'}
  ];
  ss.addMenu('登録', menu);
}

// ISBN取得&チェック関数
// ISBNを記述しているセルからデータを取得し正規表現を咬ませて、数字判定をします。
function getDataIsbn () {
  var sheet = ss.getSheetByName('登録');
  var range = sheet.getRange("C4"); // ISBNを記述しているセル
  var inputVals = range.getValue();
  var pattern = "^9784[0-9]{9}$";
  var regex = new RegExp(pattern);
  var result = String(inputVals).match(regex);

  if (result) {
    range.clearContent();
    return inputVals;
  } else {
    Browser.msgBox('この数字では登録できません');
    return;
  }
}

// 登録用シートの読み込み関数
// 別シートに登録している書籍リストからのすでに登録されている書籍のISBNを確認し、　登録が重複していないか判定
function getDataConfirmation (inputVals, lastRow) {
  var sheet2 = ss.getSheetByName('書籍リスト');
  var isbnList = sheet2.getRange(2, 6, sheet2.getLastRow() - 1).getValues();

  var overlap = isbnList.some(function(array, i, isbnList) {
    return (array[0] === inputVals);
  });
  
  if (overlap) {
    var confirmation = Browser.msgBox('この書籍はすでに登録されています。追加で登録されますか？', Browser.Buttons.OK_CANCEL);
    if (confirmation === 'ok') {
        return false;
    } else if (confirmation === 'cancel') {
      return true;
    }
  } else {
    return false;
  }
}

// APIを叩き、結果を別シートのテーブルに記述する
function setDataRegistration () {
  var sheet = ss.getSheetByName('書籍リスト');
  var inputVals = getDataIsbn();
  
  if(inputVals !== undefined) {
    var lastRow = sheet.getLastRow();
    var lastIndex = (sheet.getRange(lastRow, 1).getValue() === '') ? 0 : sheet.getRange(lastRow, 1).getValue();
    var doubleCheck = getDataConfirmation(inputVals, lastRow);
    
    if(doubleCheck === false) {
      var apiUrl = 'https://www.googleapis.com/books/v1/volumes?q=isbn:' + inputVals;
      var request = UrlFetchApp.fetch(apiUrl);
      
      if (request.getResponseCode() === 200) {
        var json = JSON.parse(request.getContentText());
        var itemData = json.items[0].volumeInfo;
        var setBookData = {
          'title': itemData.title,
          'author': itemData.authors,
          'publisher': itemData.publisher,
          'release': itemData.publishedDate,
          'image': itemData.imageLinks.thumbnail,
          'description': itemData.description,
          'details': itemData.previewLink
        };
        
        sheet.getRange(lastRow + 1, 1).setValue(lastIndex + 1);
        sheet.getRange(lastRow + 1, 2).setValue(setBookData.title);
        sheet.getRange(lastRow + 1, 3).setValue(setBookData.author);
        sheet.getRange(lastRow + 1, 4).setValue(setBookData.publisher);
        sheet.getRange(lastRow + 1, 5).setValue(setBookData.release);
        sheet.getRange(lastRow + 1, 6).setValue(inputVals);
        sheet.getRange(lastRow + 1, 10).setValue(setBookData.image);
        sheet.getRange(lastRow + 1, 11).setValue(setBookData.description);
        sheet.getRange(lastRow + 1, 12).setValue(setBookData.details);
      
        Browser.msgBox('『' + setBookData.title + '』を追加登録致しました。');
      } else {
        Browser.msgBox('APIデータが取得できませんでした。手動入力を行って下さい。');
      }
    }
  }
}
```
一つ一つのGASの機能であるイベントハンドラやメソッドについての詳細は割愛させて頂きます。
参考記事がたくさんありますし、アドカレからきた方には冗長になりそうなので...。

上記でISBNから別シートへ登録できるロジックになります。
この他にもオプションを色々付けて使い勝手の良い管理シートにしていきます。(今回は紹介しません)

## FireBase(Firestore)と連携させる
やっと表題ですね！！

**FireBase**
Googleが提供している、モバイルおよびWebアプリケーションのバックエンドで行う機能を提供するクラウドサービスです。
MBaas(Mobile Backend as a Service)の一種でバックエンド開発に必要な工程を担ってくれるのでアプリケーション開発者がクライアントサイドの開発に集中できるようになります。
私みたいなサーバサイドの知識を持たないフロントエンドエンジニアでも、アプリケーション開発を手軽に制作出来る優れものです。

**Cloud Firestore**
FireBaseの豊富なサービス群の１つ。
Googleから提供されているスケーラブルなNoSQLデータベースであり、
クライアントとサーバーモデル間のデータを保存・同期をしてくれる機能です。

現在まだベータ版のみのリリースですが、リリースしてから１年近く運用されていますし、本番運用でも安心して使用できます。（2017年10月リリース）



この記事はGASのアドカレ記事なので、FireBaseでの新規プロジェクトの作成方法などはご紹介しません。
下記の記事がわかりやすく、参考になりましたのでFireBaseを触った事がないよって方は是非ご覧になって下さい。
・[Firebaseの始め方](https://qiita.com/kohashi/items/43ea22f61ade45972881)
・[Firebase Cloud Firestoreの使い方](https://qiita.com/subaru44k/items/a88e638333b8d5cc29f2)


GASでの連携方法はこちら↓

```javascript
// 上記で記載していたツールバーの設定にDB更新メニューを追加
// ※数字部分はよしなに自分の環境に合わせて差し替えて下さい
function onOpen(){
  var menu = [
    {name: '書籍登録', functionName: 'setDataRegistration'},
    {name: 'DB更新', functionName: 'updateFirestore'}
  ];
  ss.addMenu('登録', menu); //メニューを追加
}

// 登録されている書籍のデータを取得する
function get_bookData() {
  var url = 'https://docs.google.com/spreadsheets/d/スプレットシートId';
  var book = SpreadsheetApp.openByUrl(url);
  var sheet = book.getSheetByName('書籍リスト');
  var firstRange = sheet.getRange(1, 1, 1, 12);
  var firstRowValues = firstRange.getValues();
  var titleColumns = firstRowValues[0];

  var lastRow = sheet.getLastRow();
  var rowValues = [];
  for(var rowIndex = 2; rowIndex <= lastRow; rowIndex++) {
    var colStartIndex = 1;
    var rowNum = 1;
    var range = sheet.getRange(rowIndex, colStartIndex, rowNum, sheet.getLastColumn());
    var values = range.getValues();
    rowValues.push(values[0]);
  }

  var jsonArray = [];
  for(var i = 0; i < rowValues.length; i++) {
    var line = rowValues[i];
    var json = new Object();
    for(var j=0; j<titleColumns.length; j++) {
      json[titleColumns[j]] = line[j];
    }
    jsonArray.push(json);
  }
  return jsonArray  
}


// ↓↓ firebase連携用関数 ↓↓

// Firestoreで認証する為のデータ情報を指定
function firestoreDate() {
  var dateArray = {
    'email': '[~~~~]@[PROJECT_NAME].iam.gserviceaccount.com',
    'key': '-----BEGIN PRIVATE KEY-----\xxx\n-----END PRIVATE KEY-----\n',
    'projectId': '[PROJECT_NAME]'
  }
  return dateArray;
}

// 初期データ作成
// Scriptシート上のツールバー > 実行 > 関数を実行 > setFirestore
function createFirestore() {
  var bookData = get_bookData();
  var dateArray = firestoreDate();
  var firestore = FirestoreApp.getFirestore(dateArray.email, dateArray.key, dateArray.projectId);
  firestore.createDocument('BookCollection/BookData', bookData);
}

function updateFirestore() {
  var bookData = get_bookData();
  var dateArray = firestoreDate();
  var firestore = FirestoreApp.getFirestore(dateArray.email, dateArray.key, dateArray.projectId);
  firestore.updateDocument('BookCollection/BookData', bookData);
}
```
[github:FirestoreGoogleAppsScript](https://github.com/grahamearley/FirestoreGoogleAppsScript)を参考に関数を実装していきます。

まず、Firestoreへの認証を通す必要があります。`FirestoreApp.getFirestore(email,key,projectId)`でFirestoreで認証し、Firestoreオブジェクトを取得します。
(email,key,projectId)の部分はfirestoreDate()でdateArrayに格納し、値を返すようにしています。

**記載しているパラメーターの詳細**
・email: ユーザーの電子メールアドレス（認証用）
・key: ユーザーの秘密鍵（認証用）
・projectId: FirestoreプロジェクトID
Firebase Admin APIを使用する為、秘密鍵の発行する必要があります。

**秘密鍵の発行方法**
・[Firebase Console](https://console.firebase.google.com/u/0/?hl=ja) > Setting (Project Overviwe横の歯車アイコン) > プロジェクトの設定 > サービスアカウント > 新しい秘密鍵の作成
↑上記の手順でPrivate KeyがダウンロードされるのでserviceAccountKey.jsonという名前で保存しましょう。
そのjsonファイルに記載されているclient_emailとprivate_keyの値がそれぞれ上記のemailとkeyの値になります。

認証部分が終わったらあとは簡単。
初期作成の場合は`createDocument('コレクション名/ドキュメント名', data)`, 更新したい場合は`updateDocument('コレクション名/ドキュメント名', data)`を記述して各々対象の関数を呼び出すだけになります。

<img width="944" alt="スクリーンショット 2018-12-02 02.17.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/203392/628be409-d80c-c1d0-9530-f1ff453b6ce8.png">



## あとがき

今回はアプリケーションでデータの表示行うのでGASではデータの更新のみでしたが、FireBaseのデータを取得して表示したり編集したりもGASで出来るので簡単なフォームを作成し、データを保存、別シートでドキュメントを作成するのにも役立ちそうです。

今まで『GAS×スプレットシート』しか実装した経験しかないので、年末に皆さんのアドカレ記事を参考に色々自動化ツールを作成したいと思います。