スプレッドシートとgoogle apps scriptで簡易APIを作ってalamofireからget/postする

google apps scriptには[Content Service](https://developers.google.com/apps-script/guides/content?hl=ja)というクラスがあって、GUIを伴うwebアプリケーションを作れたり、REST APIを作れたりします。今回は、GET/POSTができる簡易的なAPIを作って、[alamofire](https://github.com/Alamofire/Alamofire)とスプレッドシート間でjsonデータのやりとりをしてみたいと思います。

サーバ側がまだ準備できてないとか、サーバ用意するほどのものでもない場合に便利かと思います。

# こんな感じ
![](https://dl.dropboxusercontent.com/u/91774212/EasyGASDemo.gif)

# やりたいこと
 - GET用シートからデータをalamofireで取得
 - POST用シートにデータをalamofire経由で追加

※ 但し、alamofireからのpost時に画像は含められません(ContentServiceがmulti-partに非対応のため)

# ソース
[google apps script]
https://github.com/mitolog/GASEasyAPI

[iOS]
https://github.com/mitolog/GASEasyAPI-alamofire-client-sample

# 手順

## スプレッドシートを作成
下記の感じで作成します。
### getシート
 - 1行目を項目名にして、2行目からデータを入れます
 - シート名は *get* にしておきます

![Screen Shot 2015-05-02 at 14.14.11.png](https://qiita-image-store.s3.amazonaws.com/0/46489/b00667bb-63a5-4517-bc39-74fa72f4e3c4.png)

### postシート
 - 1行目を項目名にしておきます。2行目からはデータが入ってきます
 - シート名は *post* にしておきます

![Screen Shot 2015-05-02 at 16.44.38.png](https://qiita-image-store.s3.amazonaws.com/0/46489/a554b435-d858-7b90-9297-a248b445d08d.png)

※ 今回は便宜上postとgetのパラメータを同じにしています。

## スクリプトを作成
スプレッドシートのメニューで、ツール > スクリプトエディタ... を選択します。その後、プロジェクト名、ファイル名をそれぞれ適宜設定します。

```js:Get.gs
/**
 * Get API
 * @return json object
 */
function doGet(e) {
  
  var getSheet = getFirstMatchedFileAndSheet(g_filename, g_getSheetName);
  if(!getSheet) return;
  
  // First row must be parameter names
  var cells = getSheet.getDataRange().getValues();
  var paramNames = [];
  if(!cells || cells.length <= 1){
    return;
  }
  
  paramNames = cells[0];
  cells.shift();
  
  var userLists = [];
  cells.forEach(function(aLine, idx){
    var aDic = {};
    aLine.forEach(function(elem, elemIdx){
      aDic[paramNames[elemIdx]] = elem;
    });
    userLists.push(aDic);
  });
  
  var result = {};
  result['data'] = userLists;

  return ContentService.createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON);
  
}
```

```js:Post.gs
/**
 * Post API
 * @return json object
 */
function doPost(e) {
  
  if(!e) return;
  
  var paramStr = e.postData['contents'];
  // debug
//  var paramStr = '{"Users": [{"id": "1", "created": "2015-05-02 07:32:01 +0000", "name": "人造人間1号"},{"id": "2", "created": "2015-05-02 07:32:01 +0000", "name": "人造人間2号"}]}';
  var params = JSON.parse(paramStr);
  
  var targetSheet = getFirstMatchedFileAndSheet(g_filename, g_postSheetName);
  if(!targetSheet) return;
  
  // First row must be parameter names
  var dataRng = targetSheet.getDataRange();
  var cells = dataRng.getValues();
  if(!cells || cells.length < 1){
    return;
  }
  
  // Make data array[][] to plot spreadsheet range
  var paramNames = cells[0];
  var dataAry = [];
  params["Users"].forEach(function(aParamDic, idx){
    var plotLine = [];
    paramNames.forEach(function(paramName, pnIdx){
      var val = aParamDic[paramName];
      plotLine[pnIdx] = (val) ? val : "";
    });
    dataAry.push(plotLine);
  });
  
  // Append data after last data line
  dataRng.offset(dataRng.getNumRows(),0,dataAry.length).setValues(dataAry);
  
  var result = {};
  result['data'] = dataAry;
  
  return ContentService.createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON);
  
}
```

```js:Common.gs
var g_filename = 'GASAPIDemo';
var g_getSheetName = 'get';
var g_postSheetName = 'post';
var g_mimeTypeContainStr = 'spreadsheet';

/**
 * Driveの中から最初に見つかった、targetFileNameのスプレッドシートを取得する
 * @return SpreadSheet 
 */
function getFirstMatchedSpreadSheet(targetFileName)
{
  var resFile = null;
  
  var files = DriveApp.getFilesByName(targetFileName);
  while (files.hasNext()) {
    var file = files.next();
    if(file.getMimeType().indexOf(g_mimeTypeContainStr) != -1){
      resFile = file;
      break;
    }
  }
  return SpreadsheetApp.open(resFile);
}

/**
 * Driveの中から最初に見つかった、filenameファイルのsheetnameシート取得する
 * @return Sheet
 */
function getFirstMatchedFileAndSheet(filename, sheetname)
{
  var targetFile = getFirstMatchedSpreadSheet(filename);
  if(!targetFile){
    return null;
  }

  var targetSheet = targetFile.getSheetByName(sheetname);
  if(!targetSheet){
    return null;
  }
  return targetSheet;
}
```

## アプリの実行権限を得る
Google Apps Scriptからスプレッドシートを利用するには、オーソライズが必要なので、まずは、下記のように実行して権限を取得します。

![Screen Shot 2015-05-02 at 15.56.31.png](https://qiita-image-store.s3.amazonaws.com/0/46489/e740e1ed-304d-8f1b-45d0-66472e019c95.png)

![Screen Shot 2015-05-02 at 15.56.44.png](https://qiita-image-store.s3.amazonaws.com/0/46489/7422c5e4-2049-f5a8-50c1-8f2b838d853f.png)

![Screen Shot 2015-05-02 at 15.57.06.png](https://qiita-image-store.s3.amazonaws.com/0/46489/608109f0-c3f7-2c34-754b-c549d70ab4e9.png)

## APIを公開する
下記の手順で公開します。とりあえずスクショをぺたぺた貼っていきます。

### 新しい版(APIのversionになります)を登録
![Screen Shot 2015-05-02 at 17.43.47.png](https://qiita-image-store.s3.amazonaws.com/0/46489/38178854-74bc-f51d-f6a4-d5c598c011bd.png)
![Screen Shot 2015-05-02 at 17.44.05.png](https://qiita-image-store.s3.amazonaws.com/0/46489/386bc136-d87c-86bc-e1c2-f122d54e448d.png)

### ウェブアプリとして導入
![Screen Shot 2015-05-02 at 17.44.22.png](https://qiita-image-store.s3.amazonaws.com/0/46489/5a94f97a-1463-e331-a3c8-4f0084a26839.png)

![Screen Shot 2015-05-02 at 17.44.34.png](https://qiita-image-store.s3.amazonaws.com/0/46489/700a75c8-9251-6e65-76bf-b7127d88d6eb.png)

**※** 公開したいバージョンを選択、アプリケーションの実行ユーザは **Me**にしておいて、アクセスできるユーザーは、**全員(匿名ユーザーを含む)**にしておきます。

![Screen Shot 2015-05-02 at 17.44.50.png](https://qiita-image-store.s3.amazonaws.com/0/46489/702b4c15-7c89-11ce-2869-16669902ea2b.png)

最後に出てきた画面のURLをコピーして置きます。

## iOSアプリ側

### ソースを落とす
```bash
$ git clone git@github.com:mitolog/GASEasyAPI-alamofire-client-sample.git GASEasyAPIAlamoSmaple
$ cd GASEasyAPIAlamoSmaple
$ pod install
```
### さっきコピーしておいた、urlをコピペ
下記の部分になります。

```swift:ViewController.swift
struct AppConsts {
    
    struct HttpGetUrl {
        static let UserList = ""
    }
    
    struct HttpPostUrl {
        static let RegUser = ""
    }
}
```

以上！

# 感想 & 注意点

- multipartのpostもできたら尚良いな
- APIの公開urlはredirectされているので、AFNetworkingとかAlamofire以外で独自でやる際はredirect処理を忘れずに
- iOS側でエラーがかえってくる場合、jsonのパースで落ちる場合は、スプレッドシートの処理がおかしい可能性があるので、`function doPost(e)`のeにダミーの値をいれてためすといいです。
- eの内部構造が公式ドキュメントで解説されていない？ので、困った...([Event Object](https://developers.google.com/apps-script/guides/triggers/events?hl=ja)のページにも無いし...)
- realmとかcoredataとかのモデル部分もXcodeのプロジェクトに入れてアプリのテンプレートとして持っとくと使い回しできて良いのかも