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

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

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

![todo-list.png](https://qiita-image-store.s3.amazonaws.com/0/81715/7ac5689e-5db6-4a24-f544-4c3dc8c145d9.png)

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

## システムの全体像
<img src="https://docs.google.com/drawings/d/e/2PACX-1vTWBMhmGDK73TrU3Yq88la4vGPis_eW67wN865O2khN7fRmwzrPdWIT1wID_5QP3CxF4c-I1BUmBmZC/pub?w=631&amp;h=218">

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

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

![email.png](https://qiita-image-store.s3.amazonaws.com/0/81715/1dcb333b-be8f-2b3d-77e2-bdf2e610dee7.png)

## Github APIからToDoリストを取得する
GithubAPIのクライアントライブラリは[@octokit/rest](https://github.com/octokit/rest.js)を使用しました。
TypeScriptの宣言ファイルもしっかり用意されているので、補完がきいて楽に書けます。

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

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

### ToDoのタイトルを取得するまでの長い道のり
プロジェクト一覧を取ってくればその中にカラム名(`To do`や`In 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](https://github.com/pugjs/pug)を使用しました。
後述しますが、Webpackと[pug-loader](https://github.com/pugjs/pug-loader)を使用することで、以下のようにHTMLテキストを生成できます。

```typescript
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](https://github.com/serverless-heaven/serverless-webpack)を使用しました。

Webpackの設定はserverless-webpackの[サンプル](https://github.com/serverless-heaven/serverless-webpack/blob/master/examples/typescript/webpack.config.js)から持ってきたものをほぼそのまま使いました。
pug-loaderだけ追加しています。

```javascript: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の設定も特に特別なことはありません。

```yaml: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](https://github.com/niftylettuce/email-templates)というライブラリを使おうとしていました。
スターも結構ついていて人気そうだし、HTMLメールのプレビュー表示なんかもしてくれて便利だったので使っていたのですが、これまたWebpackでバンドルしようとした時に問題が置きました。

依存しているライブラリに[consoliate.js](https://github.com/tj/consolidate.js)というのがあって、この中で[モジュールの動的インポート](https://webpack.js.org/guides/dependency-management/#require-with-expression)をしている関係で、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](https://ja.todoist.com/)のようなToDo機能に特化したサービスに比べると、通知機能やスマホでの操作性といった点では劣ります。
しかし、GithubのAPIやIntegration機能を利用すれば、自分独自のToDoサービスが作れるという魅力もあります。
今後もGithub ProjectsでのToDo管理を便利にする機能を作っていこうと思っています。