どもども!こんにちは平社員の龍ちゃんです。今回は、Google Apps Scrip(通称:GAS)を使って「TODOリスト」を実現するためのAPIを構築していこうと思います。前回の記事(Google Apps Scriptで「REST API」を作る)では、GASの制限を回避してREST APIっぽいものを実現する方法について解説しました。今回は、その制限回避方法を使用してTODOリストのためのRESTAPIを使用していこうと思います。
前もって作成するAPIの仕様書も作りましたので、その紹介もしておきます。
API仕様書
普段であれば、APIの使用者はスプレッドシートやエクセルの形で作成しています。今回は、共有するにあたって必要となったので、WEBサイトの形式にしました。サイトのリンクはこちらです。
例としてPOSTの例を出しておきます。
コード解説
ファイル自体は、「responseに関係する部分・パラメータ受け取りに関する部分・API処理部分」の3つパートで構成されています。「API処理部分」以外は、こちらの記事で構成について説明をしています。そのため、この章では「API処理部分」に関して重点的に紹介していきます。ソース全体に関しては、この章の最後においておきます。
こちらが「API処理受け取り部分」のコードになります。
function doPost(e) {
// 返信作成用定数
const output = ContentService.createTextOutput();
output.setMimeType(ContentService.MimeType.JSON);
// PostData受け取り
const data = JSON.parse(e.postData.contents);
const path = data["path"]
const method = data["method"]
const postData = data["postData"]
let response;
switch (path) {
case "item":
response = useTodo(method, postData)
break;
default:
response = responseErrorCreate(404, "ルートの設定がされていないパラメータです")
break;
}
output.setContent(response);
return output
}
共通処理として、シートの呼び出し処理を関数化しています。
const getTodoSheet = () => {
const SheetID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
const file = SpreadsheetApp.openById(SheetID)
const sheet = file.getSheetByName("Todo")
return sheet
}
テーブル定義
TODOリストを再現するためには、データベースが必要になります。データベースとしてGoogleSheetを利用しています。一行目をデータベースのカラム名として列をそれぞれデータとして扱っています。各カラムの扱いとしては、以下の図になります。
この前提を通して、ソースの説明を行います。useTodo内にある関数と一致する形で説明していきます。
todoGet
ここでは、情報の一括取得を行っています。取得した情報はJSON形式に変換する処理を行っています。
const todoGet = (postData) => {
const sheet = getTodoSheet()
const lastRow = sheet.getLastRow()
if (lastRow == 1) return responseCreate(200, [])
// 読み込み処理
let resultData = []
const data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
data.forEach(([ID, task, created, state]) => {
resultData.push({ ID: ID, task: task, created: created, state: state })
})
return responseCreate(200, resultData)
}
エラー処理として、情報がなかった場合では空配列として情報を返信しています。
todoPost
ここでは、情報の追加を行っています。処理の流れとしては、シートの最終行の番号を取得し、その値に+1した行に情報を追記します。受け取る情報としては「task」のみであとはAPI側で情報を作成しています。IDは順福が生まれないようにUUIDを作成しています。createdは情報を現在時刻を取得して保存しています。
const todoPost = (postData) => {
const today = new Date();
const timeString = Utilities.formatDate(today, "JST", "yyyy-MM-dd HH:mm")
const uuid = Utilities.getUuid()
const task = postData["task"]
if (!task) return responseErrorCreate(400, "requestのパラメータが間違ってますね taskが存在しません。")
const sheet = getTodoSheet()
const lastRow = sheet.getLastRow()
// 書き込み処理
sheet.getRange(lastRow + 1, 1, 1, 4).setValues([[uuid, task, timeString, false]]);
return responseCreate(201, { ID: uuid, task: task, created: timeString, state: false })
}
エラー処理として、「task」情報がなかった場合はエラーを返信しています。
todoPut
ここでは、情報の変更を行っています。処理の流れとしては、シート内に含まれる情報から送信されたIDと一致する情報を検索し、一致した情報を更新します。受け取る情報は「ID/task/created/state」の4つの情報です。
const todoPut = (postData) => {
const ID = postData["ID"]
let flag = false;
const sheet = getTodoSheet();
const lastRow = sheet.getLastRow();
if (lastRow == 1) return responseErrorCreate(400, "というかそもそも情報が登録されてなくて存在しないんですけど")
// 一括取得
const data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
// 情報を編集した形で取得
const putData = data.map((value) => {
if (value[0] == ID) {
flag = true
return [postData["ID"], postData["task"], postData["created"], postData["state"]]
}
return value
})
if (!flag) return responseErrorCreate(400, "情報が存在しないまる~そのため情報が編集できへんねん")
// 書き込み処理
sheet.getRange(2, 1, lastRow - 1, 4).setValues(putData)
return responseCreate(204, {})
}
エラー処理として、登録されているデータがない場合と一致するIDがない場合に関してはエラーを出力します。
エラーハンドリングでは、期待する情報がない場合はエラーを出す必要がありますね。ですが、今回は、仕様書を作成した段階で考慮がなかったということでこのまま進めていきます。使用漏れですね。
todoDelete
ここでは、情報の削除を行っています。処理の流れとしては、シート内に含まれる情報から送信されたIDと一致する情報を検索し、そのインデックス番号を保存し、Sheetの行ごとデータを削除する「deleteRow」メソッドでデータを削除します。
const todoDelete = (postData) => {
const ID = postData["ID"]
let flag = false;
let targetRow = 0
const sheet = getTodoSheet();
const lastRow = sheet.getLastRow();
if (lastRow == 1) return responseErrorCreate(400, "というかそもそも情報が登録されてなくて存在しないんですけど")
// 一括取得
const data = sheet.getRange(2, 1, lastRow - 1, 4).getValues();
// 情報を編集した形で取得
data.forEach((value, index) => {
if (value[0] == ID) {
flag = true
targetRow = index
}
})
if (!flag) return responseErrorCreate(400, "情報が存在しないまる~そのため情報が編集できへんねん")
// 削除処理
sheet.deleteRow(targetRow+2)
return responseCreate(204, {})
}
受け取る情報は、IDのみです。エラー処理としては、登録されているデータがない場合と一致するIDがない場合に関してはエラーを出力します。
全体ソース
全体のソースです。
おわりに
お疲れ様です。今回は、計画から作成までの流れを行って、使用漏れが発覚しました。実際のプロジェクトで使用漏れが発覚するのとでは、障害の大きさが違います。ですが、使用漏れというのは簡単にシステムに入り込みます。それを体験できただけでも大きな収穫とも言えます。
本来であるならば、複数の人間を通してAPI仕様書の段階で使用漏れを除外する必要があります。
今回は、定義者・実装者すべてが僕なのでしれ~っと修正しておきます。
ではまた!!