【GitHub】octkitのREST APIをTypeScriptで使ってみた

# はじめに

[TechCommit Advent Calendar 2021](https://qiita.com/advent-calendar/2021/tech-commit) 16日目を担当させていただきます、うらさくと申します！よろしくお願いします。

今回会社でGitHubのOrganizationユーザ管理をGitHub上でやっていこうぜということで、いろいろGitHubのAPIに触れていたのですが、TypeScript（Javascript）で使用されている記事が少なく、少し苦労したところがあるので、octkitの種類から基本的なoctkitの使い方まで簡単にまとめてみました

これらを使うと、UIを使わずにユーザの登録や削除ができますし、ソースコードと一緒にユーザ一覧表を管理することで半自動でユーザの管理をすることができます

GitHubAPI興味あったけど使ったことない方や、GitHubのユーザ管理に悩まれている方のご参考になれば幸いです

# octkitとは

GitHub公式のGitHub APIのラッパーです

https://github.com/octokit

今回説明するNode.js版の他に、Ruby、.Net等で使用することができます

# APIの種類

レポジトリを見ていただくとわかるように、単にoctkitといっても結構種類があります

jsだけで見ても

- octkit.js
- rest.js
- request.js
- endpoint.js
- webhook.js
- adtions.js

などなど

全部説明するのはきりがない（正直私もほとんど知らない^^;）ので、今回は私が使用した、**rest.js**について説明したいと思います

# @octkit/rest
## @octkit/requestとの違い

javaScript（TypeScript）からGitHubのAPIを叩く場合は、rest.jsとrequest.jsのどちらかになると思います

違いを軽く説明しますと、request.jsは実施にAPIを叩くところを担当しているライブラリで、rest.jsはrequest.jsに認証やフック、ページネーションなどの便利機能を付与した最終的なAPIクライアントです

request.jsをそのまま使ってAPIを叩くことも可能ではありますが、rest.jsの便利機能を捨ててまで使うメリットはあまりないと思いますので、ここからいくつかrest.jsの使い方を説明していきます

# 用途と使い方
## 導入とインポート

まずはいつものようにnpm installを実行します

```
npm install @octokit/rest @octokit/request-error --save
```

## @octokit/request-errorについて

後ろの@octokit/request-errorってなに？と思われた方がいらっしゃると思いますが、これは@octokit/requestのエラーハンドリングで使用しているGitHub独自のエラークラスです。

API実行時に発生したExceptionは、RequestError型としてthrowされます

ですので、クライアント側でcatchするためには、このクラスも同時に使用する必要があります

こちらに関しても使用例のところで軽く触れていきます


# 用途と使い方

今回はOrganization周りで使うことが多かったので、いくつか紹介させていただきます

## Organizationにユーザを招待する

```typescript
async function sendInvitation(userId: number, teamIds?: number[]): Promise<number | RequestError> {
  const params = {
    org: OWNER,
    invitee_id: userId,
    team_ids: teamIds,
  }

  try {
    const response = await octokit.request('POST /orgs/{org}/invitations', params)
    return response.status
  } catch (e) {
    console.warn(`[WARN] ${e}`)
    const error = e as RequestError
    return error
  }
}
```

パラメータのうち、org(Organization名)とinvitee_id(招待するユーザID)は必須項目です。
team_idsは招待時にチームを指定するときのみ記載します。（Array形式）

上記の通り、エラーはRequestErrorという独自の型で帰ってくるので、catchしたエラーはキャストしてからreturnします

インターネットアクセスしますので、async/awaitを使用しています

```typescript
async function main(): Promise<void> {
  for (const member of members) {
      const inviteResponseStatus = await github.sendInvitation(userId, teamIds)
      if (inviteResponseStatus instanceof RequestError) {
        console.error(`[ERROR] Invitation ${member.username} is failed.`)
      } else {
        console.log(`[INFO] Success. Invitation ${member.username} is succeed.`)
      }
    }
  }
```

呼び出し先で返り値で分岐させることで、複数人招待する際に一人が入力ミスなどでエラーになっても、ログを出して止めずに次の処理へ進ませることができます

### ユーザ名からユーザIDを取得

```typescript
async function getUserIdFromUserName(username: string): Promise<number | RequestError> {
  try {
    const response = await octokit.request('GET /users/{username}', { username: username })
    return response.data.id as number
  } catch (e) {
    console.warn(`[WARN] ${e}`)
    const error = e as RequestError
    return error
  }
}
```

ユーザIDはUI上から確認することができないので、APIから取得する必要があります

### チーム名からチームIDを取得

```typescript
export async function getTeamIds(teams: string[]): Promise<number[]> {
  const teamIdsList: number[] = []
  for (const team of teams!) {
    const params = {
      org: OWNER,
      team_slug: team,
    }

    try {
      const response = await octokit.request('GET /orgs/{org}/teams/{team_slug}', params)
      teamIdsList.push(response.data.id)
    } catch (e) {
      console.error(`[Error] ${e}`)
      throw new Error(`[Error] Teams ${team} doesn't exist. Please check this team name.`)
    }
  }
  return teamIdsList
}
```

チームIDも同様です。こちらは配列としてreturnしています

## 現在Organizationに登録されているユーザを取得する

```typescript
async function getOrgMembers() {
  const response = await octokit.paginate('GET /orgs/{org}/members', { org: OWNER })
  const memberList = response.map((element) => {
    return { username: element.login }
  })
  return memberList
}
```

すでに登録されているユーザは上記のように取得できます

ユーザ名一覧のMapをreturnしています

ユーザ名はusernameではなく、loginで取得できる点に注意です

### Pagenationについて

リスト系のAPIでは、per_pageオプションで一度に取得する情報数を指定できますが、上限があり一度に最大100件までの情報しか取得できません。これは、GitHub REST APIの制約なので、このようなケースでは本来ならば複数回に分けてリクエストする必要がありますが、rest.jsではPagenationという便利な仕組みを使うことができます。

# まとめ

個人での使用に限ればあまり使う機会はないかもしれませんが、Organizationで使用されている方はこれらを使うことでユーザ管理をGitHub上で完結することができます

例えば、、
ユーザ一覧CSV+スクリプトのActionsファイルのGitHubレポジトリ作成
↓
ユーザ一覧CSVに登録したいユーザを追加してPull Request作成
↓
管理者がPRを確認して問題なければマージ
↓
マージ時にActionsからOrganizationへのInviteAPIを叩いてユーザを招待
などでしょうか？

こうすることでわざわざ「登録お願いします」や「登録しました」などのやり取りが不要になるだけでなく、APIを駆使してユーザの上限をチェックしたり、複数のOrganizationを管理する際にも役に立つと思います

他にもいろいろなAPIがあり、使い方は無限大なので、皆さんも色々試してみて、良いGitHubライフをお過ごしください！

ご覧頂きありがとうございました

# 参考

https://dackdive.hateblo.jp/entry/2019/11/11/100000

https://maku.blog/p/7r6gr3d/