Google Apps ScriptでTODOリスト:CRUD処理

◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【5/21開催】Azure OpenAI ServiceによるRAG実装ガイドを公開しました
生成AIを活用したユースケースで最も一番熱いと言われているRAGの実装ガイドを公開しました。そのガイドの紹介をおこなうイベントです!!
https://tech-lab.connpass.com/event/315703/

どもども!こんにちは平社員の龍ちゃんです。今回は、Google Apps Scrip(通称:GAS)を使って「TODOリスト」を実現するためのAPIを構築していこうと思います。前回の記事(Google Apps Scriptで「REST API」を作る)では、GASの制限を回避してREST APIっぽいものを実現する方法について解説しました。今回は、その制限回避方法を使用してTODOリストのためのRESTAPIを使用していこうと思います。

前もって作成するAPIの仕様書も作りましたので、その紹介もしておきます。

API仕様書

普段であれば、APIの使用者はスプレッドシートやエクセルの形で作成しています。今回は、共有するにあたって必要となったので、WEBサイトの形式にしました。サイトのリンクはこちらです。

例としてPOSTの例を出しておきます。

API仕様書 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仕様書の段階で使用漏れを除外する必要があります。

今回は、定義者・実装者すべてが僕なのでしれ~っと修正しておきます。

ではまた!!

アバター画像
About 龍:Ryu 107 Articles
2022年入社で主にフロントエンドの業務でTailwindと遊ぶ日々。お酒とうまいご飯が好きで、運動がちょっと嫌いなエンジニアです。しゃべれるエンジニアを目指しておしゃべりとブログ執筆に注力中(業務もね)//
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる