# 機能設計書 64-アバター管理（連絡先）

## 概要

本ドキュメントは、Chatwootにおける連絡先（Contact）のアバター（プロフィール画像）管理機能の設計を記述する。連絡先のアバター画像の設定、自動取得（Gravatar）、削除機能を提供する。

### 本機能の処理概要

連絡先アバター管理機能は、連絡先のプロフィール画像を管理する機能を提供する。ActiveStorageを使用したファイル添付機能を基盤とし、Gravatar連携による自動取得機能も備えている。

**業務上の目的・背景**：カスタマーサポート業務において、顧客を視覚的に識別できることは、エージェントの対応効率を向上させる。会話一覧や連絡先一覧でアバター画像が表示されることで、過去にやり取りした顧客を素早く認識でき、パーソナライズされた対応が可能になる。

**機能の利用シーン**：
- 連絡先詳細画面でアバター画像を手動アップロードする場合
- 外部URLからアバター画像を取得・設定する場合
- メールアドレス登録時にGravatarから自動でアバターを取得する場合
- 不要なアバター画像を削除する場合

**主要な処理内容**：
1. アバター画像のアップロード（ファイル直接アップロード）
2. アバター画像のURL指定による設定（非同期Job実行）
3. Gravatarからの自動取得（メールアドレス変更時）
4. アバター画像の削除

**関連システム・外部連携**：
- ActiveStorage：ファイル添付管理
- Gravatar API：メールアドレスベースのアバター自動取得
- 画像処理：image_processing gem によるリサイズ（250px）

**権限による制御**：連絡先へのアクセス権限を持つエージェントのみがアバター操作が可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 24 | 連絡先編集画面 | 主画面 | 連絡先アバターの設定・削除 |

## 機能種別

CRUD操作（Create/Read/Delete）/ データ連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | Integer | Yes | 対象連絡先ID | URLパラメータ、存在確認 |
| avatar | File | No | アバター画像ファイル | 15MB以下、jpeg/png/gif |
| avatar_url | String | No | アバター画像URL | URL形式 |

### 入力データソース

- 画面入力：ファイルアップロード
- APIパラメータ：avatar_url

## 出力仕様

### 出力データ

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

### 出力先

- API レスポンス（JSON形式）
- フロントエンド各画面（会話一覧、連絡先一覧等）

## 処理フロー

### 処理シーケンス

```
1. アバターアップロード
   └─ contacts#update で avatar パラメータを受信
   └─ ActiveStorage でファイル添付
   └─ バリデーション（サイズ、形式）

2. URL指定によるアバター設定
   └─ contacts#update で avatar_url パラメータを受信
   └─ Avatar::AvatarFromUrlJob を非同期実行
   └─ URLから画像をダウンロードして添付

3. Gravatarからの自動取得
   └─ Contact モデル保存時、email が変更された場合
   └─ Avatar::AvatarFromGravatarJob を30秒後に非同期実行
   └─ Gravatarから画像を取得して添付

4. アバター削除
   └─ contacts#avatar アクションでDELETEリクエスト
   └─ avatar.purge で添付ファイルを削除
```

### フローチャート

```mermaid
flowchart TD
    A[アバター操作リクエスト] --> B{操作種別}
    B -->|ファイルアップロード| C[バリデーション]
    B -->|URL指定| D[AvatarFromUrlJob起動]
    B -->|Gravatar自動| E[AvatarFromGravatarJob起動]
    B -->|削除| F[avatar.purge]
    C -->|OK| G[ActiveStorage添付]
    C -->|NG| H[422 Validation Error]
    D --> I[非同期でURL取得]
    E --> J[非同期でGravatar取得]
    I --> G
    J --> G
    G --> K[avatar_url生成]
    F --> L[200 OK]
    K --> M[JSONレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | サイズ制限 | アバター画像は15MB以下 | ファイルアップロード時 |
| BR-02 | 形式制限 | JPEG, PNG, GIF のみ許可 | ファイルアップロード時 |
| BR-03 | リサイズ | 表示時に250px幅にリサイズ | avatar_url取得時 |
| BR-04 | Gravatar遅延 | Gravatar取得は30秒後に実行（avatar_urlとの競合回避） | メール変更時 |
| BR-05 | Gravatar条件 | メールアドレスが変更され、かつ空でない場合のみ | メール保存時 |

### 計算ロジック

特に計算ロジックはなし。

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| upload | active_storage_attachments | INSERT | 添付ファイル関連付け |
| upload | active_storage_blobs | INSERT | ファイルメタデータ保存 |
| delete | active_storage_attachments | DELETE | 添付ファイル関連付け削除 |
| delete | active_storage_blobs | DELETE | ファイルメタデータ削除 |

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

#### active_storage_attachments

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | 'avatar' | |
| INSERT | record_type | 'Contact' | |
| INSERT | record_id | 連絡先ID | |
| INSERT | blob_id | 対応するblob.id | |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 認証トークンが無効または期限切れ | 再認証を促す |
| 404 | Not Found | 連絡先が存在しない | 正しいIDを指定 |
| 422 | Unprocessable Entity | ファイルサイズ超過または不正な形式 | 適切なファイルを選択 |

### リトライ仕様

- AvatarFromUrlJob: URL取得失敗時はジョブ失敗として処理
- AvatarFromGravatarJob: Gravatar取得失敗時はジョブ失敗として処理

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

- ファイルアップロードはActiveStorageの機構に従う
- purge は添付ファイルと Blob を一括削除

## パフォーマンス要件

- avatar_url はリサイズ処理を含むため、初回アクセス時に処理時間が発生
- リサイズ結果はキャッシュされる（ActiveStorage variant機能）

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

- アカウント単位でのアクセス制御
- ファイル形式の制限によるセキュリティリスク軽減
- ファイルサイズ制限によるDoS攻撃対策

## 備考

- Avatarable concern は Contact, User, Inbox, Company 等で共通利用
- Gravatar取得は30秒の遅延を入れることで、avatar_urlパラメータとの競合を回避

---

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

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

### 推奨読解順序

#### Step 1: Avatarable concernを理解する

アバター管理の共通ロジックはAvatarable concernに集約されている。

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

**読解のコツ**: `has_one_attached :avatar` はActiveStorageの添付ファイル定義。`representation` はバリアント（加工済み画像）の生成。

**主要処理フロー**:
- **8行目**: `has_one_attached :avatar` - ActiveStorage による1対1添付定義
- **9行目**: `validate :acceptable_avatar` - カスタムバリデーション（変更時のみ）
- **10行目**: `after_save :fetch_avatar_from_gravatar` - 保存後フック
- **13-17行目**: `avatar_url` - アバターURLを返却、リサイズ処理を含む
- **19-26行目**: `fetch_avatar_from_gravatar` - メール変更時にGravatar取得Job起動
- **28-35行目**: `acceptable_avatar` - サイズ（15MB）と形式（jpeg/png/gif）のバリデーション

#### Step 2: コントローラーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | contacts_controller.rb | `app/controllers/api/v1/accounts/contacts_controller.rb` | アバター削除アクションとURL取得処理 |

**主要処理フロー**:
- **112-115行目**: `avatar` アクション - アバター削除（purge）
- **94-98行目**: `update` アクション内で `process_avatar_from_url` を呼び出し
- **207-209行目**: `process_avatar_from_url` - avatar_urlパラメータがある場合にJobを起動
- **173行目**: `permitted_params` - :avatar, :avatar_url を許可

#### Step 3: Jobクラスを理解する

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

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

```
Api::V1::Accounts::ContactsController
    │
    ├─ update アクション
    │      ├─ contact.assign_attributes(avatar: ...)
    │      │      └─ ActiveStorage::Attached::One#attach
    │      │
    │      └─ process_avatar_from_url
    │             └─ Avatar::AvatarFromUrlJob.perform_later
    │
    ├─ avatar アクション (DELETE)
    │      └─ contact.avatar.purge
    │             └─ ActiveStorage::Attachment#purge
    │
    └─ Contact モデル（Avatarable concern）
           ├─ acceptable_avatar バリデーション
           │      ├─ byte_size > 15.megabytes → エラー
           │      └─ content_type not in [jpeg, png, gif] → エラー
           │
           └─ after_save :fetch_avatar_from_gravatar
                  └─ Avatar::AvatarFromGravatarJob.set(wait: 30.seconds).perform_later
```

### データフロー図

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

avatar (File) ───────▶ ActiveStorage#attach ─────────▶ active_storage_blobs
                              │                           (INSERT)
                              │
                       Avatarable#acceptable_avatar
                              │
                       サイズ・形式チェック

avatar_url (URL) ────▶ AvatarFromUrlJob ─────────────▶ active_storage_blobs
                       (非同期)                          (INSERT)

email (変更) ────────▶ AvatarFromGravatarJob ────────▶ active_storage_blobs
                       (30秒後、非同期)                   (INSERT)

DELETE リクエスト ──▶ avatar.purge ──────────────────▶ active_storage_blobs
                                                        (DELETE)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| avatarable.rb | `app/models/concerns/avatarable.rb` | モデルconcern | アバター管理共通ロジック |
| contacts_controller.rb | `app/controllers/api/v1/accounts/contacts_controller.rb` | コントローラー | アバター削除、URL取得処理 |
| avatar_from_url_job.rb | `app/jobs/avatar/avatar_from_url_job.rb` | ジョブ | URLからアバター取得 |
| avatar_from_gravatar_job.rb | `app/jobs/avatar/avatar_from_gravatar_job.rb` | ジョブ | Gravatarからアバター取得 |
| contact.rb | `app/models/contact.rb` | モデル | 連絡先モデル（Avatarableインクルード） |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（DELETE avatar） |
