# 機能設計書 48-メディア対応

## 概要

本ドキュメントは、Chatwootにおける画像・ビデオ・ファイル送信機能の設計を記述します。

### 本機能の処理概要

メッセージに画像、動画、音声、各種ファイルを添付して送受信する機能を提供します。Active Storageを使用してファイルを保存し、各チャネルに応じた形式でメディアを配信します。

**業務上の目的・背景**：カスタマーサポートでは、スクリーンショット、領収書、製品画像、マニュアルPDFなど、テキスト以外のコンテンツをやり取りする必要があります。メディア対応機能により、エージェントと顧客間で多様な形式のファイルを共有でき、より効果的なコミュニケーションが可能になります。

**機能の利用シーン**：
- 顧客が問題のスクリーンショットを送信する場面
- エージェントが説明用の画像やPDFを送信する場面
- 音声メッセージを送受信する場面
- 顧客が領収書や契約書などの画像を送信する場面

**主要な処理内容**：
1. ファイルのアップロードと保存（Active Storage）
2. ファイルタイプの自動判定
3. 添付ファイルレコードの作成
4. 各チャネルへのメディア配信
5. サムネイル生成（画像の場合）

**関連システム・外部連携**：
- Active Storage（ファイル保存）
- クラウドストレージ（S3, GCS等）設定時
- 各チャネルのメディアAPI

**権限による制御**：メッセージ送信権限を持つユーザーがファイル添付を利用できます。WebWidget経由の場合はファイルタイプとサイズが制限されます。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | 主画面 | 画像・ファイルの送信 |
| 116 | Widget メッセージ画面 | 主画面 | 画像・ファイルの送信 |

## 機能種別

データ管理 / ファイル処理 / データ連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| attachments | Array | No | 添付ファイルの配列 | 最大15ファイル |
| attachments[].file | File/SignedID | Yes | ファイル本体またはActive Storage Signed ID | - |

### 入力データソース

- 画面入力：会話詳細画面のファイル添付UI
- Widget：Web WidgetのファイルアップロードUI
- API：マルチパートフォームデータまたはSigned ID

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | 添付ファイルID |
| message_id | Integer | 関連メッセージID |
| file_type | String | ファイルタイプ（image/audio/video/file/location等） |
| data_url | String | ファイルダウンロードURL |
| thumb_url | String | サムネイルURL（画像の場合） |
| file_size | Integer | ファイルサイズ（バイト） |
| width | Integer | 画像幅（画像の場合） |
| height | Integer | 画像高さ（画像の場合） |

### 出力先

- API レスポンス：JSON形式で添付ファイル情報を返却
- データベース：attachmentsテーブル
- ストレージ：Active Storage（ローカルまたはクラウド）

## 処理フロー

### 処理シーケンス

```
1. ファイルアップロード受信
   └─ MessageBuilder#performでattachmentsを受け取る
2. 添付ファイル処理
   └─ process_attachmentsで各ファイルを処理
3. Attachmentレコード作成
   └─ message.attachments.buildでレコード作成
4. ファイルタイプ判定
   └─ file_type_by_signed_idまたはfile_typeで判定
5. Active Storageへの保存
   └─ has_one_attached :fileで自動保存
6. メッセージ保存
   └─ autosaveオプションで一括保存
7. チャネル配信
   └─ SendReplyJobでメディア付きメッセージを配信
```

### フローチャート

```mermaid
flowchart TD
    A[ファイルアップロード受信] --> B{attachments存在?}
    B -->|No| C[メッセージのみ作成]
    B -->|Yes| D[process_attachments開始]
    D --> E{ファイル形式?}
    E -->|SignedID| F[file_type_by_signed_id]
    E -->|File| G[file_type by content_type]
    F --> H[Attachment.build]
    G --> H
    H --> I{上限チェック}
    I -->|超過| J[エラー: exceeded maximum]
    I -->|OK| K[次のファイル処理]
    K --> L{全ファイル処理完了?}
    L -->|No| E
    L -->|Yes| M[message.save!]
    M --> N[Active Storage保存]
    N --> O[SendReplyJob]
    O --> P[チャネル配信]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-48-001 | 添付ファイル上限 | 1メッセージあたり最大15ファイル | メッセージ作成時 |
| BR-48-002 | WebWidgetファイル制限 | WebWidget経由の場合はファイルタイプとサイズを検証 | Channel::WebWidget時 |
| BR-48-003 | ファイルサイズ上限 | MAXIMUM_FILE_UPLOAD_SIZE（デフォルト40MB） | WebWidget経由時 |
| BR-48-004 | 許可ファイルタイプ | 画像/動画/音声/PDF/Office文書等の特定タイプのみ | WebWidget経由時 |
| BR-48-005 | ジョブ遅延 | 添付ファイルありの場合はSendReplyJobを2秒遅延 | メッセージ送信時 |

### 計算ロジック

**ファイルタイプ判定**:
- content_typeが`image/*`で始まる場合 → image
- content_typeが`audio/*`で始まる場合 → audio
- content_typeが`video/*`で始まる場合 → video
- それ以外 → file

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 添付ファイル作成 | attachments | INSERT | 添付ファイル情報の保存 |
| 添付ファイル削除 | attachments | DELETE | メッセージ削除時 |

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

#### attachments

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | message.account_id | - |
| INSERT | message_id | 関連メッセージID | - |
| INSERT | file_type | image/audio/video/file/location等 | enum |
| INSERT | extension | ファイル拡張子 | - |
| INSERT | external_url | 外部URL（外部リソースの場合） | - |
| INSERT | meta | メタ情報（transcribed_text等） | JSONB |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 422 | バリデーションエラー | 添付ファイルが15を超過 | ファイル数を削減 |
| 422 | バリデーションエラー | ファイルサイズが上限超過（WebWidget） | 小さいファイルを使用 |
| 422 | バリデーションエラー | 許可されていないファイルタイプ（WebWidget） | 許可されたタイプのファイルを使用 |

### リトライ仕様

Active Storageへの保存失敗時はRailsのデフォルトエラー処理に従います。

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

メッセージとその添付ファイルはautosaveオプションにより同一トランザクションで保存されます。メッセージ保存失敗時は添付ファイルも保存されません。

## パフォーマンス要件

- 大きなファイルのアップロードはActive Storageの直接アップロード機能を使用可能
- 添付ファイルありの場合は2秒の遅延後にSendReplyJobを実行（Active Storage同期待ち）
- サムネイル生成はオンデマンド（thumb_urlアクセス時）

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

- WebWidget経由の場合はファイルタイプとサイズを検証
- 許可されたファイルタイプのホワイトリスト方式
- Active Storageのsigned URLによるアクセス制御
- 外部URLの長さ制限（URL_LENGTH_LIMIT）

## 備考

- ファイルタイプはimage, audio, video, file, location, fallback, share, story_mention, contact, ig_reel, ig_post, ig_story, embedをサポート
- 音声ファイルの場合、meta.transcribed_textに音声トランスクリプションを保存可能
- Instagramからのメディアは外部URLとして保存される場合があります

---

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

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

### 推奨読解順序

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

まず、Attachmentモデルの構造を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | attachment.rb | `app/models/attachment.rb` | スキーマ定義（1-22行目）、file_type enum（42-43行目）、バリデーション（40-41行目） |

**読解のコツ**: file_typeのenumは多数の値を持ち、各チャネル固有のタイプ（ig_reel, ig_post等）も含まれます。

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

MessageBuilderでの添付ファイル処理を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | message_builder.rb | `app/builders/messages/message_builder.rb` | process_attachments（50-67行目） |

**主要処理フロー**:
1. **50-51行目**: attachmentsの存在チェック
2. **53-66行目**: 各ファイルに対してattachment.buildを実行
3. **59-65行目**: ファイルタイプの判定（SignedIDか通常ファイルか）

#### Step 3: バリデーションを理解する

WebWidget経由のファイル制限を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | attachment.rb | `app/models/attachment.rb` | acceptable_file（165-181行目）、should_validate_file?（157-163行目） |

**主要処理フロー**:
- **157-163行目**: WebWidgetの場合のみバリデーションを実行
- **168行目**: validate_file_sizeでサイズチェック
- **169行目**: validate_file_content_typeでタイプチェック

#### Step 4: 出力データを理解する

push_event_dataでのレスポンス生成を確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | attachment.rb | `app/models/attachment.rb` | push_event_data（45-49行目）、file_metadata（111-123行目） |

**主要処理フロー**:
- **45-49行目**: base_dataとfile_type別のメタデータをマージ
- **111-123行目**: file_metadataでdata_url, thumb_url, file_size等を生成

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

```
Messages::MessageBuilder#perform
    │
    ├─ process_attachments
    │      │
    │      ├─ @attachments.each
    │      │      │
    │      │      ├─ message.attachments.build
    │      │      │
    │      │      └─ file_type判定
    │      │             ├─ file_type_by_signed_id (SignedID)
    │      │             └─ file_type (File)
    │      │
    │      └─ validate_attachments_limit (before_add)
    │
    └─ message.save!
           │
           └─ autosave: true
                  │
                  └─ Attachment#save
                         │
                         ├─ acceptable_file (WebWidget)
                         │      ├─ validate_file_size
                         │      └─ validate_file_content_type
                         │
                         └─ Active Storage保存
```

### データフロー図

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

attachments[]       ───▶ MessageBuilder              ───▶ DB (attachments)
(File/SignedID)          │                                  │
                         │                                  ▼
                         ├─▶ file_type判定                  Active Storage
                         │                                  (ファイル保存)
                         │
                         └─▶ acceptable_file         ───▶ ValidationError
                              (WebWidget)                 (サイズ/タイプ違反時)

Attachment           ───▶ push_event_data            ───▶ JSON Response
                              │                            {
                              ├─ base_data                   id, message_id,
                              │                              file_type, account_id
                              └─ metadata_for_file_type      data_url, thumb_url,
                                                             file_size, width, height
                                                           }
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| attachment.rb | `app/models/attachment.rb` | モデル | 添付ファイルデータモデル |
| message_builder.rb | `app/builders/messages/message_builder.rb` | ビルダー | 添付ファイル処理 |
| message.rb | `app/models/message.rb` | モデル | 添付ファイル上限チェック（validate_attachments_limit） |
| file_type_helper.rb | `app/helpers/file_type_helper.rb` | ヘルパー | ファイルタイプ判定 |
| direct_uploads_controller.rb | `app/controllers/api/v1/accounts/conversations/direct_uploads_controller.rb` | コントローラー | 直接アップロードAPI |
