# 機能設計書 136-Twitter認可

## 概要

本ドキュメントは、Chatwootシステムにおける「Twitter認可」機能の設計仕様を記載する。本機能は、Twitter（X）DMチャネルを設定するためのOAuth 1.0a認証フローを実装する。

### 本機能の処理概要

Twitter認可機能は、企業がTwitterダイレクトメッセージ（DM）を通じた顧客対応をChatwootで管理するための認証基盤を提供する。OAuth 1.0aを使用してTwitterアカウントへのアクセス権限を取得し、DMの送受信およびWebhook購読を可能にする。

**業務上の目的・背景**：
Twitter（現X）は主要なソーシャルメディアプラットフォームの一つであり、多くの企業がカスタマーサポートチャネルとして活用している。Chatwootとの連携により、TwitterのDMを他のチャネル（チャット、メール等）と統合的に管理できるようになる。リアルタイムでのDM受信（Webhooks）と、チーム対応による効率的な顧客サポートを実現する。

**機能の利用シーン**：
- 新規にTwitter DMチャネルを追加する場合
- Twitterアカウントからのダイレクトメッセージを受信・返信する場合
- Webhookを通じてリアルタイムでメッセージを受信する場合

**主要な処理内容**：
1. Twitter OAuth 1.0a認可URL生成とユーザーリダイレクト
2. OAuthリクエストトークンの取得とRedisへの一時保存
3. OAuthアクセストークンへの変換
4. Twitterチャネルの作成とインボックス作成
5. Webhook購読の設定
6. プロフィール画像の取得とアバター設定

**関連システム・外部連携**：
- Twitter API v2
- Twitty gem（Twitter APIクライアント）
- Redis（OAuthトークン一時保存）
- Twitter Account Activity API（Webhooks）

**権限による制御**：
- Twitter認可を実行するにはAdministrator権限が必要
- 認可完了後のインボックス操作はエージェント権限でも可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 48 | インボックス新規作成画面 | 主画面 | 新しいTwitterチャネルの作成開始 |
| 49 | チャネル設定画面 | 主画面 | Twitter認可フローの開始 |
| 50 | インボックス設定画面 | 参照画面 | 認可完了後の設定画面 |

## 機能種別

OAuth認証 / 外部API連携 / データ作成

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| oauth_token | String | Yes | OAuthリクエストトークン | Redisに保存されたアカウントIDと対応 |
| oauth_verifier | String | Yes | OAuth検証コード | Twitterから発行された有効なコード |

### 入力データソース

- Twitter OAuth認証コールバック
- 管理画面からの認可開始リクエスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| redirect_url | String | Twitter認可画面へのリダイレクトURL |
| inbox_id | Integer | 作成されたインボックスID |
| twitter_channel | Object | 作成されたTwitterチャネル情報 |

### 出力先

- Redis（OAuthリクエストトークン一時保存）
- データベース（channel_twitter_profiles, inboxes テーブル）
- 管理画面へのリダイレクト
- API レスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. 認可開始リクエスト（AuthorizationsController#create）
   └─ 管理者権限チェック
   └─ Twitter APIでリクエストトークン取得
   └─ oauth_tokenをRedisに保存（account_idと紐付け）
   └─ Twitter OAuth認可URL生成

2. Twitterログイン・権限承認
   └─ ユーザーがTwitterでログイン
   └─ アプリへのアクセス権限を承認
   └─ oauth_tokenとoauth_verifierでコールバック

3. コールバック処理（CallbacksController#show）
   └─ oauth_tokenからaccount_idを取得（Redis）
   └─ アクセストークン交換
   └─ Redisからoauth_tokenを削除

4. チャネル・インボックス作成
   └─ Channel::TwitterProfile レコード作成
   └─ Inbox レコード作成
   └─ プロフィール画像取得・アバター設定

5. Webhook購読設定
   └─ Twitter::WebhookSubscribeServiceでWebhook購読

6. リダイレクト
   └─ エージェント追加画面へ
```

### フローチャート

```mermaid
flowchart TD
    A[開始: Twitter連携開始] --> B[管理者権限チェック]
    B --> C{権限あり?}
    C -->|No| D[403エラー]
    C -->|Yes| E[リクエストトークン取得]
    E --> F{取得成功?}
    F -->|No| G[422エラー]
    F -->|Yes| H[Redisにトークン保存]
    H --> I[認可URL生成]
    I --> J[JSONレスポンス返却]
    J --> K[フロントエンドがリダイレクト]
    K --> L[Twitterログイン画面]
    L --> M{認証成功?}
    M -->|No| N[エラーリダイレクト]
    M -->|Yes| O[コールバック受信]
    O --> P{denied?}
    P -->|Yes| N
    P -->|No| Q[アクセストークン交換]
    Q --> R{交換成功?}
    R -->|No| N
    R -->|Yes| S[チャネル作成]
    S --> T[インボックス作成]
    T --> U[Redisトークン削除]
    U --> V[Webhook購読設定]
    V --> W[リダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-136-01 | 管理者権限必須 | Twitter認可はAdministrator権限が必要 | 認可開始時 |
| BR-136-02 | OAuth 1.0a使用 | TwitterはOAuth 1.0aを使用（OAuth 2.0ではない） | 全認可フロー |
| BR-136-03 | Redisトークン保存 | リクエストトークンはRedisに一時保存してアカウントIDと紐付け | トークン取得後 |
| BR-136-04 | denied時の処理 | ユーザーが認可を拒否した場合はエラーページにリダイレクト | コールバック時 |
| BR-136-05 | Webhook購読 | インボックス作成後にWebhook購読を設定 | チャネル作成後 |
| BR-136-06 | トランザクション処理 | チャネル・インボックス作成はトランザクション内で実行 | チャネル作成時 |

### 計算ロジック

特になし。

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| チャネル作成 | channel_twitter_profiles | INSERT | 新規Twitterチャネル作成 |
| インボックス作成 | inboxes | INSERT | インボックス作成とチャネル関連付け |

### テーブル別操作詳細

#### channel_twitter_profiles

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | twitter_access_token | parsed_body['oauth_token'] | アクセストークン |
| INSERT | twitter_access_token_secret | parsed_body['oauth_token_secret'] | アクセストークンシークレット |
| INSERT | profile_id | parsed_body['user_id'] | TwitterユーザーID |

#### inboxes

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | parsed_body['screen_name'] | Twitterスクリーン名 |
| INSERT | channel_type | 'Channel::TwitterProfile' | チャネルタイプ |
| INSERT | channel_id | 作成されたチャネルID | ポリモーフィック関連 |
| INSERT | account_id | account.id (from Redis) | アカウントID |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | Administrator権限なし | 管理者でログインし直す |
| 422 | Unprocessable Entity | リクエストトークン取得失敗 | Twitter API設定確認 |
| - | Redirect Error | ユーザーが認可拒否（denied） | 再度認可フローを開始 |
| - | Redirect Error | アクセストークン交換失敗 | 再度認可フローを開始 |

### リトライ仕様

- トークン交換失敗時、例外をキャプチャしてエラーページにリダイレクト

## トランザクション仕様

- チャネル・インボックス作成、Redisトークン削除、Webhook購読は`ActiveRecord::Base.transaction`内で実行

## パフォーマンス要件

- Twitter API呼び出しは外部依存のため、タイムアウトを適切に設定
- Redisへのトークン保存は高速に完了

## セキュリティ考慮事項

- TWITTER_CONSUMER_KEYとTWITTER_CONSUMER_SECRETは環境変数で管理
- アクセストークンシークレットは暗号化して保存
- Redisに保存するトークンは短期間で削除

## 備考

- OAuth 1.0aを使用（TwitterはOAuth 2.0へ移行中だが、DM APIは1.0a）
- Twitty gemを使用したTwitter API操作
- Account Activity API（Webhooks）でリアルタイムDM受信

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: Twitter認証共通機能を理解する

Twitter OAuth設定の共通処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | twitter_concern.rb | `app/controllers/concerns/twitter_concern.rb` | twitter_clientの初期化、API設定 |

**主要処理フロー**:
- **6-8行目**: parsed_body - レスポンスボディのパース
- **14-21行目**: twitter_client - Twitty::Facadeの初期化
  - TWITTER_CONSUMER_KEYとTWITTER_CONSUMER_SECRETを環境変数から取得
  - base_url: api.twitter.com
  - TWITTER_ENVIRONMENT設定
- **23-25行目**: twitter_api_base_url - 'https://api.twitter.com'

#### Step 2: 認可開始処理を理解する

認可URL生成のコントローラーを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | authorizations_controller.rb | `app/controllers/api/v1/accounts/twitter/authorizations_controller.rb` | 認可URL生成 |

**主要処理フロー**:
- **6-13行目**: create アクション
  - twitter_client.request_oauth_tokenでリクエストトークン取得
  - Redis::Alfred.setex(oauth_token, account_id)でRedis保存
  - oauth_authorize_endpoint(oauth_token)で認可URL生成
- **18-20行目**: oauth_token - レスポンスからoauth_tokenを取得
- **22-24行目**: oauth_authorize_endpoint - 認可URLを構築
- **26-28行目**: check_authorization - 管理者権限チェック

#### Step 3: コールバック処理を理解する

認可コールバックの処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | callbacks_controller.rb | `app/controllers/twitter/callbacks_controller.rb` | Twitterコールバック処理 |

**主要処理フロー**:
- **4-19行目**: show アクション
  - denied パラメータチェック
  - ensure_access_tokenでアクセストークン取得
  - トランザクション内でインボックス作成
  - Redis::Alfred.delete(oauth_token)
  - Twitter::WebhookSubscribeServiceでWebhook購読
- **29-32行目**: account_id - Redisからaccount_id取得
- **39-44行目**: ensure_access_token - アクセストークン交換
- **46-58行目**: create_inbox - チャネル・インボックス作成
- **60-68行目**: save_profile_image - プロフィール画像取得・アバター設定

### プログラム呼び出し階層図

```
Api::V1::Accounts::Twitter::AuthorizationsController#create
    |
    +-- check_authorization (before_action)
    |       |
    |       +-- Current.account_user.administrator?
    |
    +-- twitter_client.request_oauth_token(url: callback_url)
    |       |
    |       +-- TwitterConcern#twitter_client
    |               |
    |               +-- Twitty::Facade.new(CONSUMER_KEY, CONSUMER_SECRET)
    |
    +-- Redis::Alfred.setex(oauth_token, Current.account.id)
    |
    +-- render json: { success: true, url: oauth_authorize_endpoint }

Twitter::CallbacksController#show
    |
    +-- denied? チェック
    |       |
    |       +-- redirect_to twitter_app_redirect_url (denied時)
    |
    +-- ensure_access_token
    |       |
    |       +-- twitter_client.access_token(oauth_token, oauth_verifier)
    |
    +-- ActiveRecord::Base.transaction
            |
            +-- create_inbox
            |       |
            |       +-- account.twitter_profiles.create!
            |       |       |
            |       |       +-- twitter_access_token
            |       |       +-- twitter_access_token_secret
            |       |       +-- profile_id
            |       |
            |       +-- account.inboxes.create!
            |       |       |
            |       |       +-- name: screen_name
            |       |       +-- channel: twitter_profile
            |       |
            |       +-- save_profile_image
            |               |
            |               +-- twitter_client.user_show
            |               +-- Avatar::AvatarFromUrlJob.perform_later
            |
            +-- Redis::Alfred.delete(oauth_token)
            |
            +-- Twitter::WebhookSubscribeService.new(inbox_id).perform
```

### データフロー図

```
[入力]                    [処理]                           [出力]

管理者リクエスト      --> AuthorizationsController#create --> 認可URL
(認可開始)               |                                  |
                         +-- request_oauth_token            +-- { success, url }
                         +-- Redis保存

Twitter OAuth        --> CallbacksController#show        --> チャネル・インボックス
(oauth_token,            |                                  |
 oauth_verifier)         +-- Redisからaccount_id取得        +-- Channel::TwitterProfile
                         +-- access_token交換               +-- Inbox
                         +-- create_inbox                   +-- Webhook購読
                         +-- WebhookSubscribeService
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| twitter_concern.rb | `app/controllers/concerns/twitter_concern.rb` | Concern | Twitter APIクライアント設定 |
| authorizations_controller.rb | `app/controllers/api/v1/accounts/twitter/authorizations_controller.rb` | コントローラー | 認可URL生成 |
| callbacks_controller.rb | `app/controllers/twitter/callbacks_controller.rb` | コントローラー | Twitterコールバック処理 |
| base_controller.rb | `app/controllers/twitter/base_controller.rb` | コントローラー | Twitter基底クラス |
| twitter_profile.rb | `app/models/channel/twitter_profile.rb` | モデル | Twitterチャネルのデータモデル |
| webhook_subscribe_service.rb | `app/services/twitter/webhook_subscribe_service.rb` | サービス | Webhook購読設定 |
| alfred.rb | `lib/redis/alfred.rb` | ライブラリ | Redis操作ヘルパー |
