# 機能設計書 33-ドラフトメッセージ

## 概要

本ドキュメントは、Chatwootにおけるドラフトメッセージ（下書き）機能の設計仕様を記載する。会話ごとにメッセージの下書きを一時保存し、エージェントが中断した入力を後で再開できるようにする機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：カスタマーサポート業務では、エージェントが長文の返信を作成中に他の緊急対応が入ったり、情報確認のために一時的に作業を中断することがある。そのような場合に入力途中のメッセージを失わないよう、ドラフト機能により作成中のメッセージを自動的に保存し、後で作業を再開できるようにする。

**機能の利用シーン**：
- 長文の返信を作成中に他の会話に対応する必要がある場合
- 情報確認のため一時的に入力を中断する場合
- 複数の会話を並行して対応する際の入力内容の保持
- ブラウザのリロードや画面遷移後に入力内容を復元する場合

**主要な処理内容**：
1. ドラフトメッセージの取得（show）：現在の会話に保存されている下書きを取得
2. ドラフトメッセージの保存（update）：入力中のメッセージをRedisに保存
3. ドラフトメッセージの削除（destroy）：下書きをクリア（メッセージ送信後など）

**関連システム・外部連携**：
- Redis：下書きデータの一時保存に使用（Redis::Alfred経由）
- 永続的なDBストレージは使用しない（一時データとして扱う）

**権限による制御**：
- 会話へのアクセス権限（ConversationPolicy#show?）を持つユーザーのみがドラフトを操作可能
- ドラフトは会話単位で管理され、全てのアクセス権を持つユーザーが共有で閲覧・編集可能

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 11 | 会話詳細画面 | 主画面 | メッセージ入力欄でのドラフト保存・復元 |

## 機能種別

データ連携（Redisストレージ）/ 一時データ管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| account_id | Integer | Yes | アカウントID（URLパラメータ） | 存在チェック |
| conversation_id | Integer | Yes | 会話のdisplay_id（URLパラメータ） | 存在チェック |
| draft_message.message | String | Yes（update時） | ドラフトメッセージ本文 | 空文字許可 |

### 入力データソース

- URLパラメータ: account_id, conversation_id
- リクエストボディ: draft_message.message

## 出力仕様

### 出力データ

#### show（ドラフト取得）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| has_draft | Boolean | ドラフトが存在するかどうか |
| message | String | ドラフトメッセージ本文（has_draft=trueの場合のみ） |

#### update / destroy

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | - | 200 OK（ボディなし） |

### 出力先

- JSON形式でHTTPレスポンスとして返却

## 処理フロー

### 処理シーケンス

```
【show - ドラフト取得】
1. リクエスト受信
   └─ account_id, conversation_idの検証

2. 会話取得・認可チェック
   └─ ConversationPolicy#show?

3. Redisからドラフト取得
   └─ キー: CONVERSATION::{conversation_id}::DRAFT_MESSAGE
   └─ Redis::Alfred.exists? でキー存在確認
   └─ Redis::Alfred.get でドラフト内容取得

4. レスポンス生成
   └─ has_draft: true/false
   └─ message: ドラフト内容（存在する場合）

【update - ドラフト保存】
1. リクエスト受信・認可チェック

2. Redisにドラフト保存
   └─ Redis::Alfred.set(key, message)

3. レスポンス生成
   └─ head :ok

【destroy - ドラフト削除】
1. リクエスト受信・認可チェック

2. Redisからドラフト削除
   └─ Redis::Alfred.delete(key)

3. レスポンス生成
   └─ head :ok
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B[会話取得]
    B --> C{認可チェック}
    C -->|失敗| D[403 Forbidden]
    C -->|成功| E{アクション判定}

    E -->|show| F{Redisにキー存在?}
    F -->|Yes| G[ドラフト取得]
    F -->|No| H[has_draft: false]
    G --> I[has_draft: true + message返却]

    E -->|update| J[Redisに保存]
    J --> K[200 OK]

    E -->|destroy| L[Redisから削除]
    L --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-33-1 | 一時保存 | ドラフトはRedisに保存され、永続的なDBには保存されない | 常時 |
| BR-33-2 | 会話単位管理 | 1会話につき1つのドラフトのみ保存可能 | 常時 |
| BR-33-3 | 共有ドラフト | ドラフトは会話へのアクセス権を持つ全ユーザーで共有される | 常時 |
| BR-33-4 | 空メッセージ許可 | 空文字列もドラフトとして保存可能 | update時 |

### 計算ロジック

- Redisキー形式: `CONVERSATION::{conversation_id}::DRAFT_MESSAGE`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| show | conversations | SELECT | 会話の存在確認・認可チェック |
| update | conversations | SELECT | 会話の存在確認・認可チェック |
| destroy | conversations | SELECT | 会話の存在確認・認可チェック |

**注意**: ドラフトデータ自体はRedisに保存されるため、DBテーブルへの書き込みは発生しない。

### Redisキー操作

| 操作 | Redisキー | 操作種別 | 概要 |
|-----|-----------|---------|------|
| show | CONVERSATION::{id}::DRAFT_MESSAGE | EXISTS, GET | キー存在確認と値取得 |
| update | CONVERSATION::{id}::DRAFT_MESSAGE | SET | ドラフト保存 |
| destroy | CONVERSATION::{id}::DRAFT_MESSAGE | DEL | ドラフト削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | NotFound | 会話が存在しない | 正しいconversation_idを指定 |
| 403 | Forbidden | 会話へのアクセス権限がない | 権限を持つユーザーでアクセス |

### リトライ仕様

- Redis接続エラー時は標準的なRedis再接続ロジックに従う
- アプリケーションレベルでの特別なリトライ処理は実装されていない

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

- DBトランザクションは使用しない
- Redis操作は単一コマンドで完結するため、アトミック性が保証される

## パフォーマンス要件

- Redisを使用しているため、レイテンシは数ミリ秒程度
- ドラフトサイズに制限はないが、長大なテキストは避けることを推奨

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

- ConversationPolicy#show?による会話アクセス権限チェック
- ドラフトは暗号化されずにRedisに保存される
- Redisの接続セキュリティは環境設定に依存

## 備考

- ドラフトにTTL（有効期限）は設定されていない
- メッセージ送信後にドラフトを明示的に削除する必要がある
- 複数ユーザーが同時編集した場合、後から保存した内容で上書きされる

---

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

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

### 推奨読解順序

#### Step 1: Redisキー定義を理解する

まず、Redisで使用されるキーの形式を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | redis_keys.rb | `lib/redis/redis_keys.rb` | CONVERSATION_DRAFT_MESSAGEキーの定義 |

**読解のコツ**: Redisキーは`CONVERSATION::{id}::DRAFT_MESSAGE`の形式で定義されている（11行目）。

#### Step 2: Redis操作クラスを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | alfred.rb | `lib/redis/alfred.rb` | Redis操作のラッパーメソッド群 |

**主要処理フロー**:
- **10-11行目**: set - 値の設定
- **20-22行目**: get - 値の取得
- **24-26行目**: delete - キーの削除
- **34-36行目**: exists? - キーの存在確認

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | draft_messages_controller.rb | `app/controllers/api/v1/accounts/conversations/draft_messages_controller.rb` | ドラフト操作のエンドポイント |
| 3-2 | base_controller.rb | `app/controllers/api/v1/accounts/conversations/base_controller.rb` | 共通の会話取得・認可ロジック |

**主要処理フロー（draft_messages_controller.rb）**:
- **2-6行目**: show - ドラフト存在確認と取得
  - Redis::Alfred.exists?でキー存在チェック
  - Redis::Alfred.getでドラフト内容取得
- **8-11行目**: update - ドラフト保存
  - Redis::Alfred.setでドラフト保存
- **13-16行目**: destroy - ドラフト削除
  - Redis::Alfred.deleteでドラフト削除
- **21-23行目**: draft_redis_key - Redisキー生成
- **25-27行目**: draft_message_params - リクエストパラメータ抽出

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

```
Api::V1::Accounts::Conversations::DraftMessagesController
    │
    ├─ BaseController#conversation
    │      └─ Current.account.conversations.find_by!
    │      └─ authorize @conversation, :show?
    │             └─ ConversationPolicy#show?
    │
    ├─ show
    │      └─ draft_redis_key
    │      └─ Redis::Alfred.exists?
    │      └─ Redis::Alfred.get
    │
    ├─ update
    │      └─ draft_redis_key
    │      └─ draft_message_params
    │      └─ Redis::Alfred.set
    │
    └─ destroy
           └─ draft_redis_key
           └─ Redis::Alfred.delete
```

### データフロー図

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

conversation_id ───▶ DraftMessagesController ───▶ JSON Response
draft_message           │                            (has_draft, message)
                        ▼
              draft_redis_key生成
              CONVERSATION::{id}::DRAFT_MESSAGE
                        │
                        ▼
                Redis::Alfred
                        │
                        ▼
                   Redis (Cache)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| draft_messages_controller.rb | `app/controllers/api/v1/accounts/conversations/draft_messages_controller.rb` | コントローラー | ドラフトのCRUD処理 |
| base_controller.rb | `app/controllers/api/v1/accounts/conversations/base_controller.rb` | コントローラー | 共通処理（会話取得・認可） |
| alfred.rb | `lib/redis/alfred.rb` | ライブラリ | Redis操作ラッパー |
| redis_keys.rb | `lib/redis/redis_keys.rb` | ライブラリ | Redisキー定義 |
| conversation_policy.rb | `app/policies/conversation_policy.rb` | ポリシー | アクセス権限制御 |
| routes.rb | `config/routes.rb` | 設定 | ルーティング定義（142行目） |
