# 機能設計書 162-Widgetメッセージ

## 概要

本ドキュメントは、Chatwoot WebウィジェットにおけるWidgetメッセージ管理機能の設計を記載する。この機能はエンドユーザー（訪問者）がWebサイト上のチャットウィジェットを通じてメッセージを送受信するためのAPIを提供する。

### 本機能の処理概要

Widgetメッセージ機能は、Webサイト訪問者がチャットウィジェット経由でテキストメッセージや添付ファイルを送受信するための機能を提供する。訪問者はメッセージの送信、過去メッセージの取得、メッセージの更新（メールアドレス入力やCSATアンケートの回答）を行うことができる。

**業務上の目的・背景**：Webサイト訪問者とサポートエージェント間のメッセージ通信を実現するため、ウィジェット側からメッセージを管理するAPIが必要である。これにより、訪問者は質問を送信し、エージェントからの回答を受け取り、添付ファイルを共有してスムーズなカスタマーサポート体験を得ることができる。

**機能の利用シーン**：
- 訪問者がチャットウィジェットでメッセージを入力・送信する場面
- 訪問者が過去のメッセージ履歴を確認する場面
- 訪問者が画像やファイルを添付して送信する場面
- メールアドレス入力フォームに回答する場面
- CSATアンケートに回答する場面

**主要な処理内容**：
1. メッセージ一覧の取得（ページネーション対応）
2. 新規メッセージの作成（テキスト・添付ファイル）
3. メッセージの更新（メール入力・アンケート回答）

**関連システム・外部連携**：
- ActiveStorage（添付ファイル管理）
- WebSocket（ActionCable）によるリアルタイム通知

**権限による制御**：
- website_tokenによるウィジェット認証が必要
- プライベートメッセージとアクティビティメッセージは自動的にフィルタリング

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 116 | メッセージ画面 | 主画面 | チャットメッセージの送受信 |
| 117 | 未読メッセージ画面 | 参照画面 | 未読メッセージの通知表示 |

## 機能種別

CRUD操作 / ファイルアップロード

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| website_token | String | Yes | ウェブウィジェットの識別トークン | 有効なトークンである必要がある |
| before | Integer | No | このID以前のメッセージを取得 | 正の整数 |
| after | Integer | No | このID以降のメッセージを取得 | 正の整数 |
| message.content | String | No | メッセージ内容 | - |
| message.referer_url | String | No | 参照元URL | - |
| message.timestamp | Integer | No | メッセージのタイムスタンプ | - |
| message.echo_id | String | No | クライアント側のエコーID | - |
| message.reply_to | Integer | No | 返信先メッセージID | - |
| message.attachments | Array[File] | No | 添付ファイル配列 | - |
| contact.name | String | No | 連絡先の名前 | - |
| contact.email | String | No | 連絡先のメールアドレス | メール形式 |
| submitted_email | String | No | メール入力フォームの回答 | メール形式 |
| submitted_values | Array | No | アンケート回答値 | - |

### 入力データソース

- Webウィジェット（JavaScript SDK）からのHTTPリクエスト
- マルチパートフォームデータ（ファイルアップロード時）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| payload | Array | メッセージ一覧 |
| payload[].id | Integer | メッセージID |
| payload[].content | String | メッセージ内容 |
| payload[].message_type | Integer | メッセージタイプ（0:incoming, 1:outgoing, 2:activity） |
| payload[].content_type | String | コンテンツタイプ（text, input_email, input_csat等） |
| payload[].content_attributes | Object | コンテンツ属性 |
| payload[].created_at | Integer | 作成日時（UNIX時間） |
| payload[].conversation_id | Integer | 会話の表示ID |
| payload[].attachments | Array | 添付ファイル一覧 |
| payload[].sender | Object | 送信者情報 |
| meta.contact_last_seen_at | Integer | 連絡先の最終閲覧時刻 |

### 出力先

- JSON形式のHTTPレスポンス

## 処理フロー

### 処理シーケンス

```
1. メッセージ取得（index）
   └─ 会話の存在確認
   └─ MessageFinderによるメッセージ検索
   └─ プライベート・アクティビティメッセージのフィルタリング
   └─ 最新20件または指定条件のメッセージを返却

2. メッセージ作成（create）
   └─ 会話が存在しない場合は新規作成
   └─ メッセージレコードの作成
   └─ 添付ファイルの処理
   └─ メッセージの保存

3. メッセージ更新（update）
   └─ メッセージの取得
   └─ content_typeによる分岐
      ├─ input_email: メールアドレス更新と連絡先識別
      └─ その他: submitted_valuesの更新
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{アクション判定}

    B -->|index| C{会話存在?}
    C -->|No| D[空配列を返却]
    C -->|Yes| E[MessageFinder実行]
    E --> F[フィルタリング]
    F --> G[レスポンス返却]

    B -->|create| H{会話存在?}
    H -->|No| I[会話作成]
    H -->|Yes| J[メッセージ作成]
    I --> J
    J --> K{添付ファイル?}
    K -->|Yes| L[添付ファイル処理]
    K -->|No| M[メッセージ保存]
    L --> M
    M --> G

    B -->|update| N[メッセージ取得]
    N --> O{content_type?}
    O -->|input_email| P[メール更新]
    P --> Q[連絡先識別]
    O -->|その他| R[submitted_values更新]
    Q --> G
    R --> G
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-162-01 | メッセージフィルタリング | プライベートメッセージ(private=true)とアクティビティメッセージ(message_type=2)は除外 | index時 |
| BR-162-02 | ページネーション | before指定時は20件、after指定時は100件、両方指定時は1000件を上限 | index時 |
| BR-162-03 | 会話自動作成 | 会話が存在しない場合はメッセージ作成時に自動作成 | create時 |
| BR-162-04 | メール入力時の連絡先更新 | input_emailタイプのメッセージ更新時は連絡先も更新 | update時 |
| BR-162-05 | 連絡先名の自動設定 | retain_original_contact_nameがtrueの場合、既存の名前を保持 | update時（input_email） |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| index | messages | SELECT | メッセージ一覧取得 |
| create | messages | INSERT | 新規メッセージ作成 |
| create | attachments | INSERT | 添付ファイル作成（存在時） |
| create | conversations | INSERT | 会話作成（存在しない場合） |
| update | messages | UPDATE | メッセージ更新 |
| update | contacts | UPDATE | 連絡先更新（input_email時） |

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

#### messages

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | - | private != true AND message_type != 2 | フィルタ条件 |
| SELECT | - | id < before または id > after | ページネーション |
| INSERT | account_id | conversation.account_id | アカウントID |
| INSERT | inbox_id | conversation.inbox_id | インボックスID |
| INSERT | sender_id | contact.id | 送信者（連絡先） |
| INSERT | sender_type | 'Contact' | 送信者タイプ |
| INSERT | content | message.content | メッセージ内容 |
| INSERT | echo_id | message.echo_id | クライアントエコーID |
| INSERT | message_type | :incoming | 受信メッセージ |
| INSERT | content_attributes | { in_reply_to: reply_to } | 返信先情報 |
| UPDATE | submitted_email | パラメータ値 | input_email時 |
| UPDATE | submitted_values | パラメータ値 | アンケート回答時 |

#### attachments

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | account_id | message.account_id | アカウントID |
| INSERT | file | uploaded_attachment | ActiveStorageファイル |
| INSERT | file_type | content_typeから判定 | ファイル種別 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | website_tokenが無効 | 正しいトークンを使用 |
| 404 | Not Found | メッセージが存在しない | 正しいメッセージIDを指定 |
| 500 | Internal Server Error | 連絡先バリデーションエラー | エラー内容を確認 |

### リトライ仕様

特になし（同期処理のため）

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

- create: メッセージ作成と添付ファイル作成は暗黙的トランザクション
- update: 単一レコードの更新のため暗黙的トランザクション

## パフォーマンス要件

- メッセージ取得: 300ms以内
- メッセージ作成: 500ms以内（添付ファイルなし）
- 添付ファイル付きメッセージ: ファイルサイズに依存

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

- website_tokenによるウィジェット認証
- プライベートメッセージの自動フィルタリング
- 添付ファイルのcontent_type検証

## 備考

- 添付ファイルのfile_typeはcontent_typeから自動判定される
- 会話が存在しない場合のメッセージ作成時、自動的に会話が作成される
- 返信先メッセージはcontent_attributesのin_reply_toに格納される

---

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

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

### 推奨読解順序

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

まず、メッセージに関連するデータモデルを理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | message.rb | `app/models/message.rb` | メッセージモデルの構造、message_type、content_type |
| 1-2 | attachment.rb | `app/models/attachment.rb` | 添付ファイルモデル、file_type |

**読解のコツ**: message_typeのenum定義（incoming, outgoing, activity）とcontent_typeの種類を確認すること。

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

処理の起点となるコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | messages_controller.rb | `app/controllers/api/v1/widget/messages_controller.rb` | 各アクションの実装 |
| 2-2 | base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | 共通処理（message_params） |

**主要処理フロー**:
1. **5-7行目**: index処理（会話存在確認とMessageFinder実行）
2. **9-13行目**: create処理（会話自動作成、メッセージ作成、添付ファイル処理）
3. **15-28行目**: update処理（content_typeによる分岐処理）
4. **32-43行目**: build_attachment（添付ファイルの処理）

#### Step 3: メッセージ検索処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | message_finder.rb | `app/finders/message_finder.rb` | メッセージ検索のページネーションロジック |

**主要処理フロー**:
- **17-20行目**: フィルタリング（private, message_type=2を除外）
- **23-33行目**: ページネーション条件分岐（before, after, 両方, なし）
- **35-48行目**: 各検索パターンの実装（制限件数の違い）

#### Step 4: ビュー（レスポンス形式）を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | index.json.jbuilder | `app/views/api/v1/widget/messages/index.json.jbuilder` | メッセージ一覧のレスポンス形式 |
| 4-2 | create.json.jbuilder | `app/views/api/v1/widget/messages/create.json.jbuilder` | メッセージ作成のレスポンス形式 |

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

```
Api::V1::Widget::MessagesController
    │
    ├─ BaseController (認証・共通処理)
    │      ├─ set_web_widget
    │      ├─ set_contact
    │      └─ create_conversation
    │
    ├─ MessageFinder (メッセージ検索)
    │      ├─ messages (フィルタリング)
    │      ├─ messages_before
    │      ├─ messages_after
    │      ├─ messages_between
    │      └─ messages_latest
    │
    ├─ build_attachment (添付ファイル処理)
    │      └─ attachments.new
    │
    └─ ContactIdentifyAction (連絡先識別・input_email時)
           └─ perform
```

### データフロー図

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

website_token ────────▶ BaseController#set_web_widget ────▶ @web_widget
                              │
before/after ─────────▶ MessageFinder#perform ────────────▶ @messages
                              │
message_params ───────▶ conversation.messages.new ────────▶ @message
                              │
attachments ──────────▶ build_attachment ─────────────────▶ Attachment
                              │
                        JSON Response ────────────────────▶ クライアント
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| messages_controller.rb | `app/controllers/api/v1/widget/messages_controller.rb` | ソース | Widgetメッセージのメインコントローラー |
| base_controller.rb | `app/controllers/api/v1/widget/base_controller.rb` | ソース | Widget APIの基底コントローラー |
| message_finder.rb | `app/finders/message_finder.rb` | ソース | メッセージ検索・ページネーション |
| contact_identify_action.rb | `app/actions/contact_identify_action.rb` | ソース | 連絡先識別処理 |
| message.rb | `app/models/message.rb` | ソース | メッセージモデル |
| attachment.rb | `app/models/attachment.rb` | ソース | 添付ファイルモデル |
| index.json.jbuilder | `app/views/api/v1/widget/messages/index.json.jbuilder` | テンプレート | メッセージ一覧レスポンス |
| create.json.jbuilder | `app/views/api/v1/widget/messages/create.json.jbuilder` | テンプレート | メッセージ作成レスポンス |
| update.json.jbuilder | `app/views/api/v1/widget/messages/update.json.jbuilder` | テンプレート | メッセージ更新レスポンス |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（394行目） |
