# 機能設計書 133-Facebook認可

## 概要

本ドキュメントは、Chatwootシステムにおける「Facebook認可」機能の設計仕様を記載する。本機能は、Facebook Pages APIと連携するためのOAuth認証フローを実装する。

### 本機能の処理概要

Facebook認可機能は、企業がFacebook Messengerを通じた顧客対応をChatwootで一元管理するための認証基盤を提供する。管理者がFacebookアカウントでログインし、管理するFacebookページをChatwootと連携させることで、Messengerメッセージの送受信が可能になる。

**業務上の目的・背景**：
Facebookは世界最大のソーシャルメディアプラットフォームであり、多くの企業がFacebook Messengerを顧客コミュニケーションチャネルとして活用している。Chatwootとの連携により、複数のFacebookページからのメッセージを統一的に管理し、チームでの効率的な顧客対応が可能になる。これにより、レスポンス時間の短縮と顧客満足度の向上を実現する。

**機能の利用シーン**：
- 新規にFacebook Messengerチャネルを追加する場合
- 既存のFacebookページ連携を再認可する場合
- トークンの期限切れによる再認証が必要な場合
- Instagram Business Account連携のためのFacebook認証を行う場合

**主要な処理内容**：
1. Facebook OAuth認可URL生成とユーザーリダイレクト
2. 短期アクセストークンから長期アクセストークンへの変換
3. 管理可能なFacebookページ一覧の取得
4. ページ選択とインボックス作成
5. Instagram Business AccountのID取得（存在する場合）
6. ページアバターの設定
7. 既存連携の再認可処理

**関連システム・外部連携**：
- Facebook Graph API
- Koala gem（Facebook API クライアント）
- Instagram Business Account API
- Facebook OAuth 2.0認証サーバー

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

## 関連画面

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

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| omniauth_token | String | Yes | Facebookから取得した短期アクセストークン | 有効なトークン |
| user_access_token | String | Yes | ユーザーアクセストークン | 有効なトークン |
| page_access_token | String | Yes | ページアクセストークン | 有効なトークン |
| page_id | String | Yes | FacebookページID | Facebook内で一意 |
| inbox_name | String | Yes | 作成するインボックス名 | 任意の文字列 |
| inbox_id | Integer | No（再認可時必須） | 再認可対象のインボックスID | 存在するインボックス |

### 入力データソース

- Facebook OAuth認証コールバック
- 管理画面でのユーザー入力（インボックス名）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| inbox_id | Integer | 作成されたインボックスID |
| facebook_channel | Object | 作成されたFacebookチャネル情報 |
| page_details | Array | 利用可能なFacebookページ一覧 |
| instagram_id | String | 関連するInstagram Business Account ID（存在する場合） |

### 出力先

- データベース（channel_facebook_pages, inboxes テーブル）
- 管理画面への表示
- API レスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. 認可開始リクエスト
   └─ 管理者がFacebook連携を選択

2. Facebook OAuth認証
   └─ Facebookログイン画面へリダイレクト
   └─ ユーザーがFacebookでログイン・権限承認
   └─ 短期アクセストークン取得

3. 長期トークンへの変換
   └─ Koala::Facebook::OAuthでトークン交換
   └─ 有効期限の長いトークンを取得

4. Facebookページ一覧取得
   └─ Graph APIで管理可能なページ一覧取得
   └─ 既存連携ページをマーク

5. ページ選択とチャネル作成
   └─ 管理者がページを選択
   └─ Channel::FacebookPage レコード作成
   └─ Inbox レコード作成

6. Instagram Business Account確認
   └─ ページに紐づくInstagram Business Accountを取得
   └─ instagram_idをチャネルに保存

7. アバター設定
   └─ Facebookページのプロフィール画像をインボックスアバターに設定
```

### フローチャート

```mermaid
flowchart TD
    A[開始: Facebook連携開始] --> B[Facebook OAuth認証]
    B --> C{認証成功?}
    C -->|No| D[エラー画面]
    C -->|Yes| E[短期トークン取得]
    E --> F[長期トークンへ変換]
    F --> G[Facebookページ一覧取得]
    G --> H[ページ選択UI表示]
    H --> I[管理者がページ選択]
    I --> J{既存連携あり?}
    J -->|Yes| K[再認可処理]
    J -->|No| L[新規チャネル作成]
    K --> M[トークン更新]
    L --> N[Channel::FacebookPage作成]
    N --> O[Inbox作成]
    O --> P[Instagram ID取得試行]
    P --> Q[アバター設定]
    M --> P
    Q --> R[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-133-01 | 管理者権限必須 | Facebook認可はAdministrator権限が必要 | 認可開始時 |
| BR-133-02 | 長期トークン変換 | 短期トークンは長期トークンに変換して保存 | トークン取得後 |
| BR-133-03 | ページ重複防止 | 同一アカウント内で同じFacebookページは一度のみ連携可能 | チャネル作成時 |
| BR-133-04 | Instagram自動連携 | Facebookページに紐づくInstagram Business Accountがあれば自動取得 | チャネル作成/再認可時 |
| BR-133-05 | 再認可時トランザクション | 再認可処理はトランザクション内で実行 | 再認可時 |

### 計算ロジック

特になし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| チャネル作成 | channel_facebook_pages | INSERT | 新規Facebookチャネル作成 |
| インボックス作成 | inboxes | INSERT | インボックス作成とチャネル関連付け |
| 再認可 | channel_facebook_pages | UPDATE | トークン情報の更新 |

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

#### channel_facebook_pages

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | page_id | params[:page_id] | FacebookページID |
| INSERT | user_access_token | long_lived_token | 長期ユーザートークン |
| INSERT | page_access_token | params[:page_access_token] | ページアクセストークン |
| INSERT | instagram_id | Graph API取得値 | Instagram Business Account ID |
| UPDATE | user_access_token | 新しい長期トークン | 再認可時 |
| UPDATE | page_access_token | 新しいページトークン | 再認可時 |

#### inboxes

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | Facebookトークンが無効 | 再認証を実行 |
| 403 | Forbidden | ページへのアクセス権限なし | Facebookでの権限確認 |
| 422 | Unprocessable Entity | 再認可時にページが見つからない | ページの存在確認 |
| 500 | Internal Server Error | トークン交換失敗 | 再試行またはFacebookステータス確認 |

### リトライ仕様

- トークン交換失敗時、自動リトライは行わず、エラーをログ出力してユーザーに通知

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

- 新規チャネル作成：`ActiveRecord::Base.transaction`内でチャネルとインボックスを作成
- 再認可処理：`ActiveRecord::Base.transaction`内でトークン更新とInstagram ID取得を実行

## パフォーマンス要件

- Facebook Graph API呼び出しは外部依存のため、タイムアウトを適切に設定
- ページ一覧取得はページネーション対応（next_pageでループ）

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

- アクセストークンは暗号化して保存
- FB_APP_IDとFB_APP_SECRETは環境変数で管理
- CSRF対策としてstate パラメータを使用

## 備考

- Koala gemを使用したFacebook API操作
- Instagram連携はFacebook認可の一部として実装
- 長期トークンの有効期限は約60日（Facebookの仕様に依存）

---

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

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

### 推奨読解順序

#### Step 1: エントリーポイントを理解する

Facebook認可のメインコントローラーを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | callbacks_controller.rb | `app/controllers/api/v1/accounts/callbacks_controller.rb` | Facebook認可の全体フロー |

**主要処理フロー**:
- **4-23行目**: register_facebook_page - 新規ページ登録（トランザクション内でチャネル・インボックス作成）
- **32-41行目**: facebook_pages - 管理可能なページ一覧取得（ページネーション対応）
- **43-52行目**: set_instagram_id - Instagram Business Account ID取得
- **55-67行目**: reauthorize_page - 既存連携の再認可処理
- **91-94行目**: fb_object - Koala APIクライアント初期化
- **96-101行目**: long_lived_token - 短期トークンから長期トークンへの変換

#### Step 2: 長期トークン変換を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | callbacks_controller.rb | `app/controllers/api/v1/accounts/callbacks_controller.rb` | long_lived_token メソッド |

**主要処理フロー**:
- **96-101行目**: Koala::Facebook::OAuthを使用したトークン交換
  - FB_APP_IDとFB_APP_SECRETをGlobalConfigServiceから取得
  - exchange_access_token_infoで長期トークンを取得

#### Step 3: ページ一覧取得を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | callbacks_controller.rb | `app/controllers/api/v1/accounts/callbacks_controller.rb` | facebook_pages メソッド |

**主要処理フロー**:
- **32-41行目**: ページ一覧取得
  - get_connections('me', 'accounts')でページ一覧取得
  - next_pageでページネーション処理
  - mark_already_existing_facebook_pagesで既存連携をマーク

#### Step 4: Instagram連携を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | callbacks_controller.rb | `app/controllers/api/v1/accounts/callbacks_controller.rb` | set_instagram_id メソッド |

**主要処理フロー**:
- **43-52行目**: Instagram Business Account取得
  - get_connections('me', '', { fields: 'instagram_business_account' })
  - instagram_business_accountが存在する場合のみチャネルを更新

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

```
Api::V1::Accounts::CallbacksController#register_facebook_page
    |
    +-- ActiveRecord::Base.transaction
    |       |
    |       +-- Current.account.facebook_pages.create!
    |       |       |
    |       |       +-- page_id, user_access_token, page_access_token
    |       |
    |       +-- Current.account.inboxes.create!
    |       |       |
    |       |       +-- name: inbox_name, channel: facebook_channel
    |       |
    |       +-- set_instagram_id
    |       |       |
    |       |       +-- Koala::Facebook::API.new(page_access_token)
    |       |       +-- get_connections('me', '', { fields: 'instagram_business_account' })
    |       |       +-- facebook_channel.update(instagram_id: ...)
    |       |
    |       +-- set_avatar
    |               |
    |               +-- Avatar::AvatarFromUrlJob.perform_later

Api::V1::Accounts::CallbacksController#facebook_pages
    |
    +-- fb_object
    |       |
    |       +-- long_lived_token
    |       |       |
    |       |       +-- Koala::Facebook::OAuth.new(FB_APP_ID, FB_APP_SECRET)
    |       |       +-- exchange_access_token_info
    |       |
    |       +-- Koala::Facebook::API.new(@user_access_token)
    |
    +-- get_connections('me', 'accounts')
    |
    +-- mark_already_existing_facebook_pages

Api::V1::Accounts::CallbacksController#reauthorize_page
    |
    +-- fb_object
    +-- get_connections('me', 'accounts')
    +-- update_fb_page
            |
            +-- ActiveRecord::Base.transaction
                    |
                    +-- fb_page.update!(user_access_token, page_access_token)
                    +-- set_instagram_id
                    +-- fb_page.reauthorized!
```

### データフロー図

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

Facebook OAuth       --> CallbacksController             --> 長期トークン
(短期トークン)            |                                  |
                         +-- long_lived_token               +-- user_access_token

Graph API            --> facebook_pages                  --> ページ一覧
(認証情報)               |                                  |
                         +-- get_connections                +-- page_details[]
                         +-- mark_already_existing          +-- exists フラグ

管理者選択           --> register_facebook_page          --> チャネル・インボックス
(page_id,                |                                  |
 inbox_name)             +-- facebook_pages.create!         +-- Channel::FacebookPage
                         +-- inboxes.create!                +-- Inbox
                         +-- set_instagram_id               +-- instagram_id
                         +-- set_avatar                     +-- Avatar
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| callbacks_controller.rb | `app/controllers/api/v1/accounts/callbacks_controller.rb` | コントローラー | Facebook認可のメイン処理 |
| facebook_page.rb | `app/models/channel/facebook_page.rb` | モデル | Facebookチャネルのデータモデル |
| avatar_from_url_job.rb | `app/jobs/avatar/avatar_from_url_job.rb` | ジョブ | URLからアバター取得・設定 |
| global_config_service.rb | `app/services/global_config_service.rb` | サービス | FB_APP_ID等の設定値取得 |
