# 機能設計書 23-アバター管理

## 概要

本ドキュメントは、Chatwootにおけるインボックスのアバター（プロフィール画像）管理機能について記述する。この機能は、インボックスの視覚的な識別性を高めるためのアバター画像のアップロード・表示・削除を管理する。

### 本機能の処理概要

**業務上の目的・背景**：複数のインボックスを運用する組織において、各インボックスを視覚的に識別しやすくすることは、エージェントの業務効率向上に寄与する。アバター画像により、ダッシュボードやウィジェット上でインボックスを直感的に識別でき、顧客向けにはブランドイメージを統一した表示が可能となる。

**機能の利用シーン**：
- インボックス作成・編集時のアバター画像設定
- Web Widgetでの表示アイコンとしての利用
- ダッシュボード上でのインボックス一覧表示
- エージェントが複数インボックスを切り替える際の視覚的識別

**主要な処理内容**：
1. アバター画像のアップロードとActiveStorageによる保存
2. アバター画像のサイズ変換（250px幅）と表示URL生成
3. アバター画像の削除
4. Gravatarからの自動アバター取得（メールアドレス設定時）

**関連システム・外部連携**：
- ActiveStorageによるファイル保存（ローカル/S3/GCS等）
- Gravatarサービス（オプショナルな自動アバター取得）
- Sidekiqによる非同期アバター取得ジョブ

**権限による制御**：インボックスのアバター設定変更は管理者権限を持つユーザーのみ実行可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 50 | インボックス設定画面 | 主画面 | アバター画像のアップロード・削除 |
| 47 | インボックス一覧画面 | 参照画面 | アバター画像の表示 |

## 機能種別

CRUD操作 / ファイル管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| avatar | File | No | アバター画像ファイル | 最大15MB、JPEG/PNG/GIFのみ |

### 入力データソース

- 画面入力: インボックス設定画面からのファイルアップロード
- 外部サービス: Gravatarからの自動取得（メールアドレスベース）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| avatar_url | String | アバター画像のURL（250px幅にリサイズ） |

### 出力先

- ファイルストレージ: ActiveStorageによる保存（設定により異なる）
- APIレスポンス: インボックス情報内のavatar_url

## 処理フロー

### 処理シーケンス

```
1. アバターアップロード
   └─ インボックス更新時にavatarパラメータを含める
   └─ ActiveStorageがファイルを処理・保存
   └─ acceptable_avatarバリデーションでファイル検証

2. アバター表示
   └─ avatar_urlメソッドが呼ばれる
   └─ representation(resize_to_fill: [250, nil])で変換
   └─ 署名付きURLを生成

3. アバター削除
   └─ DELETE /api/v1/accounts/:account_id/inboxes/:id/avatar
   └─ avatar.attachment.destroy!で削除

4. Gravatar自動取得
   └─ emailカラム変更時にafter_saveが発火
   └─ AvatarFromGravatarJobが30秒後に実行
   └─ Gravatar URLを取得しAvatarFromUrlJobで保存
```

### フローチャート

```mermaid
flowchart TD
    A[アバター操作] --> B{操作種別}
    B -->|アップロード| C[ファイル受け取り]
    B -->|削除| D[DELETE API呼び出し]
    B -->|自動取得| E[email変更検知]

    C --> F{バリデーション}
    F -->|OK| G[ActiveStorage保存]
    F -->|NG| H[エラー返却]

    G --> I[完了]
    H --> I

    D --> J[attachment.destroy!]
    J --> K[head :ok]
    K --> I

    E --> L{条件チェック}
    L -->|既にアバターあり| I
    L -->|Gravatar無効| I
    L -->|条件OK| M[AvatarFromGravatarJob]
    M --> N[Gravatar URL生成]
    N --> O[AvatarFromUrlJob]
    O --> P[ダウンロード&アタッチ]
    P --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-23-01 | ファイルサイズ制限 | アバター画像は最大15MBまで | アップロード時 |
| BR-23-02 | ファイル形式制限 | JPEG、PNG、GIFのみ許可 | アップロード時 |
| BR-23-03 | 表示サイズ | 250px幅にリサイズして表示 | 表示時 |
| BR-23-04 | Gravatar自動取得 | メールアドレス設定・変更時に既存アバターがなければGravatarを確認 | email変更時 |
| BR-23-05 | Gravatar遅延実行 | Gravatar取得は30秒遅延で実行（avatar_url処理完了を待つ） | email変更時 |

### 計算ロジック

**Gravatar URL生成**:
```ruby
hash = Digest::MD5.hexdigest(email)
gravatar_url = "https://www.gravatar.com/avatar/#{hash}?d=404"
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| アバターアップロード | active_storage_attachments | INSERT | アタッチメント関連レコード作成 |
| アバターアップロード | active_storage_blobs | INSERT | BLOBレコード作成 |
| アバター削除 | active_storage_attachments | DELETE | アタッチメント関連レコード削除 |

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

#### active_storage_attachments

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | record_type | "Inbox" | 関連モデルタイプ |
| INSERT | record_id | inbox.id | 関連レコードID |
| INSERT | name | "avatar" | アタッチメント名 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ValidationError | ファイルサイズが15MBを超過 | "is too big"エラーを追加 |
| - | ValidationError | サポート外のファイル形式 | "filetype not supported"エラーを追加 |
| - | Down::NotFound | Gravatar画像が存在しない | エラーログ出力、処理をスキップ |
| - | Down::Error | ダウンロード失敗 | エラーログ出力、処理をスキップ |

### リトライ仕様

Gravatar/URL取得ジョブはSidekiqのデフォルトリトライ機構を使用。

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

アバターアップロードはActiveStorageの標準的なトランザクション処理に従う。

## パフォーマンス要件

- ファイルサイズ上限: 15MB
- 画像変換: オンデマンドで実行（初回アクセス時）
- Gravatarジョブ遅延: 30秒
- ジョブキュー: `purgable`（低優先度）

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

- ファイル形式バリデーションによるアップロード制限
- Punditポリシーによる認可チェック
- ActiveStorageの署名付きURLによるアクセス制御
- DISABLE_GRAVATAR設定によるGravatar無効化オプション

## 備考

- Avatarableは共通Concernとしてインボックス以外にも連絡先、ユーザー、エージェントボット等で使用
- Gravatar機能は環境変数`DISABLE_GRAVATAR`で無効化可能
- 連絡先のアバター取得にはレート制限（1分）とURL重複チェックが適用される

---

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

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

### 推奨読解順序

#### Step 1: データ構造を理解する

Avatarable Concernの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | avatarable.rb | `app/models/concerns/avatarable.rb` | has_one_attached、バリデーション、Gravatar連携 |

**読解のコツ**:
- **8行目**: `has_one_attached :avatar`でActiveStorage関連付け
- **9行目**: `validate :acceptable_avatar`でカスタムバリデーション
- **10行目**: `after_save :fetch_avatar_from_gravatar`でGravatar自動取得

**主要処理フロー**:
1. **13-17行目**: `avatar_url`で250px幅にリサイズしたURLを生成
2. **19-26行目**: `fetch_avatar_from_gravatar`でメール変更時にGravatarジョブを起動
3. **28-35行目**: `acceptable_avatar`でファイルサイズ・形式をバリデーション

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

APIコントローラーのavatarアクションを把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | avatarアクション（削除） |

**主要処理フロー**:
- **25-28行目**: `avatar`アクションでアタッチメントを削除

#### Step 3: 非同期ジョブを理解する

Gravatar/URL取得ジョブの構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | avatar_from_gravatar_job.rb | `app/jobs/avatar/avatar_from_gravatar_job.rb` | GravatarからURL取得 |
| 3-2 | avatar_from_url_job.rb | `app/jobs/avatar/avatar_from_url_job.rb` | URLからダウンロード&アタッチ |

**主要処理フロー**:
- **4-12行目** (gravatar): 条件チェック後、MD5ハッシュでGravatar URLを生成しAvatarFromUrlJobを起動
- **15-34行目** (url): Down gemでダウンロード、avatar.attachでアタッチ
- **38-48行目** (url): `should_sync_avatar?`で連絡先のレート制限・重複チェック

#### Step 4: モデルでの使用を理解する

インボックスモデルでのAvatarableの使用を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | inbox.rb | `app/models/inbox.rb` | Avatarable include |

**主要処理フロー**:
- **44行目**: `include Avatarable`でConcernをインクルード

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

```
InboxesController
    │
    ├─ #update (avatarパラメータ含む)
    │      │
    │      └─ Inbox#update!
    │             │
    │             └─ Avatarable#acceptable_avatar (バリデーション)
    │                    └─ ActiveStorage::Attached::One#attach
    │
    ├─ #avatar (DELETE)
    │      │
    │      └─ avatar.attachment.destroy!
    │
    └─ [email変更時]
           │
           └─ Avatarable#fetch_avatar_from_gravatar
                  │
                  └─ Avatar::AvatarFromGravatarJob (30秒遅延)
                         │
                         └─ Avatar::AvatarFromUrlJob
                                │
                                ├─ Down.download
                                └─ avatar.attach
```

### データフロー図

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

ファイルアップロード ───▶ バリデーション ───▶ ActiveStorage保存
                              │
                              ▼
                       acceptable_avatar
                       (サイズ/形式チェック)

email変更 ───▶ after_save ───▶ AvatarFromGravatarJob
                                      │
                                      ▼
                              MD5ハッシュ生成
                                      │
                                      ▼
                              AvatarFromUrlJob
                                      │
                                      ▼
                              Down.download
                                      │
                                      ▼
                              avatar.attach ───▶ ActiveStorage保存
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| avatarable.rb | `app/models/concerns/avatarable.rb` | Concern | アバター共通機能 |
| inbox.rb | `app/models/inbox.rb` | モデル | インボックスモデル（Avatarable使用） |
| inboxes_controller.rb | `app/controllers/api/v1/accounts/inboxes_controller.rb` | コントローラー | avatarアクション |
| avatar_from_gravatar_job.rb | `app/jobs/avatar/avatar_from_gravatar_job.rb` | ジョブ | Gravatar URL取得 |
| avatar_from_url_job.rb | `app/jobs/avatar/avatar_from_url_job.rb` | ジョブ | URLからダウンロード |
| contact.rb | `app/models/contact.rb` | モデル | 連絡先（Avatarable使用、参考） |
| user.rb | `app/models/user.rb` | モデル | ユーザー（Avatarable使用、参考） |
