めちゃめちゃ便利な日程調整ツール（以下の日程でご都合いかがでしょうかメーカー）をT3 Stackを使って作った話

## はじめに
少しキャッチーなタイトルにしました。すみません。

今回は、T3 Stack を使用して、そこそこ便利な日程調整ツール（以下日程でご都合いかがでしょうかメーカー）を作成した方法について解説します。

Google カレンダーと連携した「以下の日程でご都合いかがでしょうかメーカー」になります。

プロダクトの名前は、**UnSchedule**です。

https://unschedule.unreact.jp/?from=qiita

## Google カレンダーと連携した「以下の日程でご都合いかがでしょうかメーカー」の挙動について
最初に、Google カレンダーと連携した「以下の日程でご都合いかがでしょうかメーカー」である**UnSchedule**の挙動について解説します。

使い方は非常にシンプルで、Google ログインを行った後に、カレンダーの期間を選択するだけです。

自動的にカレンダーの空いている時間を提案する、『以下の日程でご都合いかがでしょうか』の文章を作成してくれます。

![Videotogif.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/879da91f-a747-0bf5-2845-41456ff8f30f.gif)

就業時間の設定や、休日の設定等を行うことが出来ます。

上記の動作で出来上がった文章が以下になります。

```
以下の日程でご都合いかがでしょうか。
10月21日（月）09:00〜12:00, 13:00〜13:30, 14:00〜17:00
10月22日（火）09:00〜10:30, 11:30〜12:30, 13:00〜16:00
10月23日（水）09:00〜10:00, 11:45〜12:30, 13:00〜17:00
10月24日（木）09:00〜12:30, 13:00〜13:30, 15:15〜17:00
```

## 作成した経緯
社内メンバーとの話し合いで、「日程調整って、基本的にはGoogleカレンダーの空いている時間を提案しているだけだよね」という話題が出たため、とりあえず試しに作ってみることにしました。

以前、エンジニア向けのタイピングゲームを作成した際に、社内用に作成したT3 Stackのテンプレートがあったため、今回はそれをそのまま流用してプロダクトを作成しました。

## 技術スタック
T3 Stack の構成で作成しました。

- Next.js
- TypeScript
- tRPC
- Prisma
- TailwindCSS
- Auth.js
という構成ですね。

データベースには Supabase を使用し、デプロイ先は Vercel に設定しています。今回のプロジェクトでは、Next.js の App Router を採用しました。


## 開発した機能について
以下に、今回のプロダクト（Google カレンダーと連携した「以下の日程でご都合いかがでしょうかメーカー」）を作成する際に開発した機能についてまとめていきます。

### Google ログイン機能
Google のログイン機能を作成しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411258/d9777822-fcc1-39da-ca1b-e05c2035e3e5.png)

フロントの認証情報の管理は、`NextAuth.js` に任せています

`adapter`に`PrismaAdapter`を設定することで、認証情報のデータを Prisma を介して丸っとデータベース（今回は Supabaseを利用しています）に格納しています。

```ts
adapter: PrismaAdapter(db) as Adapter
```

### Google カレンダーの空いている時間帯を取得する機能
ユーザーが選択した期間における、Google カレンダーの空いている時間を取得する機能を作成しました。

以下のコードです。

```ts
const auth = new google.auth.OAuth2({
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
});

const accountFromDb = await db.account.findFirst({
  where: { userId: ctx.session.user.id },
  select: { id: true, access_token: true, expires_at: true },
});

auth.setCredentials({
  access_token: accountFromDb?.access_token,
});

const calendar = google.calendar({ version: 'v3', auth: auth });

const timeZone = (await calendar.settings.get({ setting: 'timezone' })).data.value;

const response = await calendar.freebusy.query({
  requestBody: {
    timeMin: startDate,
    timeMax: endDate,
    items: [{ id: 'primary' }],
    timeZone,
  },
});
```

こちらのコードで、Google カレンダーの空いている時間を取得しました。

上記のコードについて、ざっくりと解説します。

#### 1. OAuth2を使った認証の設定

```ts
const auth = new google.auth.OAuth2({
  clientId: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
});
```

ここでは、GoogleのOAuth2クライアントを初期化しています。環境変数に保存されたクライアントIDとクライアントシークレットを使って、GoogleのAPIにアクセスするための認証情報を設定します。

#### 2. データベースからアクセストークンを取得
```ts
const accountFromDb = await db.account.findFirst({
  where: { userId: ctx.session.user.id },
  select: { id: true, access_token: true, expires_at: true },
});
```

次に、データベースから、現在ログインしているユーザーのアクセストークンとその期限を取得しています。このトークンは、Google Calendar APIに対してリクエストを行うために必要なものです。

#### 3. アクセストークンの設定
```ts
auth.setCredentials({
  access_token: accountFromDb?.access_token,
});
```

ここで、取得したアクセストークンをOAuth2クライアントに設定します。これにより、Google Calendar APIに対して認証されたリクエストが行えるようになります。

#### 4. Google Calendar APIクライアントの初期化
```ts
const calendar = google.calendar({ version: 'v3', auth: auth });
```

Google Calendar APIのクライアントを作成しています。このクライアントを使って、カレンダーに関する操作（例: 空き時間の確認など）を行うことができます。


#### 5. ユーザーのタイムゾーンを取得
```ts
const timeZone = (await calendar.settings.get({ setting: 'timezone' })).data.value;
```

Google Calendar APIの設定を利用して、ユーザーのタイムゾーン情報を取得します。この情報は、後のクエリで適切な時間を扱うために使います。


#### 6. 空き時間のクエリ
```ts
const response = await calendar.freebusy.query({
  requestBody: {
    timeMin: startDate,
    timeMax: endDate,
    items: [{ id: 'primary' }],
    timeZone,
  },
});
```

この部分では、Google Calendar APIの`freebusy.query`を使って、指定された期間（`startDate`と`endDate`）のユーザーの空き時間を問い合わせます。`items`で`primary`を指定しているため、ユーザーのメインカレンダーが対象になります。タイムゾーン情報も含めてリクエストを行い、結果が`response`に返されます。

## 悩んだこと
Supabaseのスペックを抑えたのが原因か、tRPCの利用が影響しているのかは不明ですが、APIのレスポンスがかなり遅いです。

また、Google カレンダーの データを取得する API も、若干遅いです。

そのため、日程を選択した後に 3 秒ほど待たされることになります。


待ち時間を短縮するため、ページ読み込み時にあらかじめ1ヶ月分のカレンダーデータを取得するように修正しました。その結果、日程調整のテキストが爆速で生成されるようになりました。

## 削った機能
複数の期間を選択する機能は削除しました。最終的には日程調整用のテキストが生成されるだけなので、不要な部分は手動で消す方が、ユーザー体験が向上すると判断したためです。

また、特定の時間（昼休みなど）を除外する機能も検討しましたが、実装は見送りました。Google カレンダーにその時間を予定として入れてもらう方がシンプルで、ユーザーにもわかりやすいと考えたからです。

## 終わりに
今回は、Google カレンダーと「以下の日程でご都合いかがでしょうかメーカー」である、**UnSchedule**の技術スタックと実装について紹介しました。

是非、一度使用して頂ければと思います。

https://unschedule.unreact.jp/?from=qiita

お疲れさまでした。