AWS
TypeScript
serverless
github-projects

Serverless+TypeScript+Github Projectsを使ったToDoリスト通知サービス

これまで様々なToDo管理ツールを試してきましたが、どれも少し使ってはやめるということを繰り返してきました。
最近使い始めてこれは続きそうだと感じているのが、GithubのProjects機能を利用したToDo管理です。
Githubは仕事で毎日使っていてインタフェースに慣れているし、Projects機能を使ってみたかったというのもあります。

こんな感じでプライベートや仕事のToDoを管理しています(ToDo少な...)。

todo-list.png

他のToDo管理ツールにはよくあるリストの通知機能が欲しくなったので、Serverless(AWS)とTypeScriptで作ってみました。

システムの全体像

GithubからToDoリストを取得してメール送信するというプログラムを、AWS Lambdaで動かしています。
LambdaはCloudWatchEventsで毎朝9時に起動され、SESを経由して自分のGmailにHTMLメールを送信するという流れです。

送られてくる内容のイメージはこんな感じです。
個人で使うだけなのでデザインは一切なし!そのうち考える!

email.png

Github APIからToDoリストを取得する

GithubAPIのクライアントライブラリは@octokit/restを使用しました。
TypeScriptの宣言ファイルもしっかり用意されているので、補完がきいて楽に書けます。

例えばあるリポジトリ上のProject一覧の取得は以下のように書きます。

const {data: projects} = await octokit.projects.getRepoProjects({
    owner: 'リポジトリオーナー名',
    repo: 'リポジトリ名'
});

ToDoのタイトルを取得するまでの長い道のり

プロジェクト一覧を取ってくればその中にカラム名(To doIn progress)や各ToDoのタイトルが入っているだろうなどと甘い考えでいたら、これがそうではなくToDoのタイトルを取得できるまで多くのAPIを叩かなければなりませんでした。。

  1. プロジェクト一覧を取得する: octokit.projects.getRepoProjects()
  2. プロジェクト内のカラム一覧を取得する: octokit.projects.getProjectColumns()
  3. カラム内のカード一覧を取得する: octokit.projects.getProjectCards()
  4. カードの詳細を取得する: octokit.projects.getProjectCard()
  5. カードの詳細データからIssueのURLを取り出し、Issueを取得する: octokit.issues.get()
  6. Issueの詳細データからタイトルを取り出す

Issue(ToDo)の数が多ければ多いほど叩くAPIの数も増えてしまうので、ToDoが増えてくるとこのやり方は破綻しますね。。
SearchAPIを使う方法も検討しましたが、このAPIだとIssueとProjectの関連付けがされていないため、IssueがどのProjectやカラムに紐付いているのかがわからず、やりたいことが実現できませんでした。

うまいやり方ご存知の方、是非ご教授ください :bow: :sweat_drops:

HTMLメールの送信

ToDoリストが取得できたら、それをHTMLに反映させます。
テンプレートエンジンはPugを使用しました。
後述しますが、Webpackとpug-loaderを使用することで、以下のようにHTMLテキストを生成できます。

const emailTemplate = require('./templates/html.pug')
const html = emailTemplate({projects: todoList})

require('./templates/html.pug')はHTML生成用の関数をロードします。
この関数にToDoリストデータを渡すことで、HTMLにToDoリストが描画されます。

デプロイ(Serverless+Webpack)

実装はTypeScriptで行ったので、これをコンパイルしてからLambda上にデプロイする必要があります。
デプロイにはServerlessプラグインのserverless-webpackを使用しました。

Webpackの設定はserverless-webpackのサンプルから持ってきたものをほぼそのまま使いました。
pug-loaderだけ追加しています。

webpack.config.js
const path = require('path');
const slsw = require('serverless-webpack');

module.exports = {
  entry: slsw.lib.entries,
  resolve: {
    extensions: [
      '.js',
      '.json',
      '.ts',
      '.tsx'
    ]
  },
  output: {
    libraryTarget: 'commonjs',
    path: path.join(__dirname, '.webpack'),
    filename: '[name].js'
  },
  target: 'node',
  module: {
    rules: [
      {
        test: /\.ts(x?)$/,
        use: [
          {
            loader: 'ts-loader'
          }
        ]
      },
      {
        test: /\.pug$/,
        use: [
          {
            loader: 'pug-loader'
          }
        ]
      }
    ]
  }
};

Serverlessの設定も特に特別なことはありません。

serverless.yml
service: todo-notification

plugins:
  - serverless-webpack

provider:
  name: aws
  runtime: nodejs8.10
  region: ap-northeast-1
  profile: ${opt:profile}
  iamRoleStatements:
    - Effect: 'Allow'
      Action:
        - 'ses:SendEmail'
      Resource:
        - 'arn:aws:ses:us-east-1:*'

functions:
  notifyTodos:
    handler: src/index.notifyTodos
    events:
      - schedule:
          name: ${self:service}-${opt:stage, self:provider.stage}-notifyTodos
          rate: cron(0 0,23 * * ? *)
    timeout: 20
    memorySize: 256

詰まったところ

実装はぶっちゃけ大したもんじゃないです。
一番時間がかかったのはWebpackでした。
Webpackに悩まされました:expressionless:

@octokit/restをWebpackでバンドルすると実行時エラーが起きる

@octokit/restの最新バージョン(この時点ではv15.4.0)をWebpackでバンドルすると、内部で使われているnode-fetchモジュールの実行に失敗しました。
fetch is not a functionというエラーになるのです。
一旦バージョンをv15.1.3まで落とすことで回避できました。
この問題に対してはすでにPRも出ていて、すぐに対応されそうです。
https://github.com/octokit/rest.js/issues/830

Webpackのrequire with expression問題

最初はHTMLの生成にemail-templatesというライブラリを使おうとしていました。
スターも結構ついていて人気そうだし、HTMLメールのプレビュー表示なんかもしてくれて便利だったので使っていたのですが、これまたWebpackでバンドルしようとした時に問題が置きました。

依存しているライブラリにconsoliate.jsというのがあって、この中でモジュールの動的インポートをしている関係で、Webpackが依存関係をうまく解決できずエラーになってしまうのです。

ERROR in ./node_modules/consolidate/lib/consolidate.js
Module not found: Error: Can't resolve 'atpl' in '/path/to/todo-notification/node_modules/consolidate/lib'
 @ ./node_modules/consolidate/lib/consolidate.js 573:51-66
 @ ./node_modules/consolidate/index.js
 @ ./node_modules/email-templates/lib/index.js
 @ ./src/index.ts

この問題はIssueに上がっていて、紹介されている解決方法も試してみたのですがうまくいかず、最終的にpug-loaderを使う方法を選択しました。
https://github.com/tj/consolidate.js/issues/295

最後に

Github ProjectsでのToDo管理はシンプルで使いやすいですが、一方でtodoistのようなToDo機能に特化したサービスに比べると、通知機能やスマホでの操作性といった点では劣ります。
しかし、GithubのAPIやIntegration機能を利用すれば、自分独自のToDoサービスが作れるという魅力もあります。
今後もGithub ProjectsでのToDo管理を便利にする機能を作っていこうと思っています。