# 機能設計書 17-テキスト追加

## 概要

本ドキュメントは、Etherpadにおけるテキスト追加機能（appendText）の設計仕様を定義する。この機能は、指定されたパッドの既存テキストの末尾に新しいテキストを追加するためのAPIを提供する。

### 本機能の処理概要

テキスト追加機能は、パッドの既存テキストを保持したまま、その末尾に新しいテキストを追加する機能である。既存コンテンツを破壊せずにテキストを追記できるため、ログ形式の記録やインクリメンタルな更新に適している。

**業務上の目的・背景**：
既存のパッド内容を保持しながら追記したい場合に使用される。議事録への追記、ログの蓄積、複数ソースからの情報集約など、既存コンテンツを破壊せずに情報を追加するユースケースに対応する。setTextとは異なり、既存の書式情報も保持される。

**機能の利用シーン**：
- 議事録への逐次追記
- 自動化されたログの蓄積
- 複数システムからの情報集約
- ボットによるメッセージ追加
- タイムスタンプ付きのエントリ追加

**主要な処理内容**：
1. 対象パッドの存在確認
2. テキストパラメータの検証
3. 既存テキストの末尾（終端改行の直前）に新テキストを挿入
4. リビジョンとして記録
5. 接続中クライアントへの変更通知

**関連システム・外部連携**：
- REST API（PATCH /api/2/pads/text）を通じて外部システムから呼び出し可能
- Socket.IO経由で接続中クライアントにリアルタイム反映

**権限による制御**：
- APIキーまたはOAuth2トークンによる認証が必要
- パッドへの書き込み権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | 本機能はAPIのみで提供され、直接関連する画面はない |

## 機能種別

データ更新（Update操作 - 追記）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| padID | string | Yes | テキストを追加するパッドのID | 正規表現に一致、存在するパッドであること |
| text | string | Yes | 追加するテキスト | 文字列型であること |
| authorId | string | No | 変更を行う著者のID（デフォルト: 空文字列） | 任意の文字列 |

### 入力データソース

REST API経由でのHTTPリクエスト（PATCH /api/2/pads/text）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| code | number | 結果コード（0: 成功、1: エラー、4: 認証エラー） |
| message | string | 結果メッセージ（"ok" または エラーメッセージ） |
| data | null | 成功時はnull |

### 出力先

- HTTPレスポンス（JSON形式）
- Socket.IO経由で接続中クライアントに変更通知

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ PATCH /api/2/pads/text
2. 認証チェック
   └─ APIキーまたはOAuth2トークンの検証
3. パラメータ抽出
   └─ padID, text, authorIdの取得
4. テキスト検証
   └─ 文字列型であることを確認
5. パッド取得
   └─ getPadSafe(padID, true)
6. テキスト追加
   └─ pad.appendText(text, authorId)
   └─ 内部でspliceText→appendRevision
7. クライアント通知
   └─ padMessageHandler.updatePadClients(pad)
8. レスポンス返却
   └─ {code: 0, message: "ok", data: null}
```

### フローチャート

```mermaid
flowchart TD
    A[開始: appendText API呼び出し] --> B{認証チェック}
    B -->|失敗| C[401 Unauthorized]
    B -->|成功| D{textは文字列?}
    D -->|No| E[400 text is not a string]
    D -->|Yes| F[パッド存在確認]
    F -->|存在しない| G[400 padID does not exist]
    F -->|存在する| H[現在のテキスト長取得]
    H --> I[末尾改行の直前位置を計算]
    I --> J[spliceText実行]
    J --> K[appendRevision]
    K --> L[DB保存]
    L --> M[updatePadClients]
    M --> N[接続クライアントに通知]
    N --> O[200 OK]
    O --> P[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-017-01 | 末尾追加 | テキストは既存テキストの末尾改行の直前に挿入 | 常時 |
| BR-017-02 | 既存保持 | 既存のテキストと書式は保持される | 常時 |
| BR-017-03 | リビジョン記録 | 変更は新しいリビジョンとして記録される | 常時 |
| BR-017-04 | 著者記録 | authorId指定時は著者として記録 | authorId指定時 |
| BR-017-05 | リアルタイム同期 | 変更は接続中クライアントに即座に通知 | 常時 |

### 計算ロジック

挿入位置の計算:
```
insertPosition = text().length - 1  // 末尾改行の直前
```

テキスト追加のChangeset生成:
```
spliceText(insertPosition, 0, newText, authorId)
```
- 削除文字数は0（追記のみ）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 参照 | pad:{padID} | SELECT | パッドメタデータ取得 |
| 更新 | pad:{padID} | UPDATE | atext, head, pool更新 |
| 挿入 | pad:{padID}:revs:{n} | INSERT | 新リビジョン追加 |
| 更新 | globalAuthor:{authorId} | UPDATE | 著者のパッド関連付け |

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

#### pad:{padID}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | atext | 追記後のテキストとその属性 | appendText後の状態 |
| UPDATE | head | +1（新リビジョン番号） | リビジョン番号インクリメント |
| UPDATE | pool | 著者属性追加 | authorId指定時 |

#### pad:{padID}:revs:{n}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | changeset | テキスト追加のChangeset | 新リビジョン |
| INSERT | meta.author | authorId | 変更者 |
| INSERT | meta.timestamp | 現在時刻 | 変更日時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 1 | apierror | パッドが存在しない | 正しいパッドIDを指定 |
| 1 | apierror | textが文字列でない | 文字列としてtextを送信 |
| 4 | authError | 認証情報が無効 | 正しいAPIキーまたはトークンを使用 |

### リトライ仕様

データベース操作に関してはueberDB2のリトライ機構に依存。

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

pad更新とrevs挿入はPromise.allで並列実行される。setTextと同様の整合性特性を持つ。

## パフォーマンス要件

- 追記するテキストサイズに比例した処理時間
- 既存テキストの長さには影響を受けにくい（末尾への挿入のため）
- 接続クライアント数に応じた通知時間

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

- APIキーまたはOAuth2認証が必須
- 入力テキストはcleanText処理でサニタイズされる
- 既存コンテンツは保持されるため、setTextより破壊的影響は小さい

## 備考

- 空文字列を追加しても、改行が追加される可能性がある（実装による）
- 連続した追加はリビジョンが急増するため、バッチ処理を検討すべき
- 末尾改行の直前に挿入されるため、追加テキストは改行で始まることが多い

---

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

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

### 推奨読解順序

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

パッドのテキスト追加における内部構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Pad.ts | `src/node/db/Pad.ts` | appendText関数（320-322行目） |
| 1-2 | Pad.ts | `src/node/db/Pad.ts` | spliceText関数（284-299行目） |

**読解のコツ**: appendTextはspliceTextへの委譲で、挿入位置は`text().length - 1`（末尾改行の直前）。

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

REST APIからの呼び出しフローを追跡する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RestAPI.ts | `src/node/handler/RestAPI.ts` | API定義（1219-1245行目でappendTextのマッピング） |
| 2-2 | APIHandler.ts | `src/node/handler/APIHandler.ts` | パラメータ定義（136行目でappendTextの引数） |

**主要処理フロー**:
1. **1219-1245行目**: PATCH /pads/text のルート定義
2. **136行目**: appendTextの引数（padID, text, authorId）

#### Step 3: ビジネスロジックを理解する

実際のテキスト追加ロジックを読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | API.ts | `src/node/db/API.ts` | appendText関数（230-239行目） |
| 3-2 | Pad.ts | `src/node/db/Pad.ts` | appendText関数（320-322行目） |
| 3-3 | Pad.ts | `src/node/db/Pad.ts` | spliceText関数（284-299行目） |

**主要処理フロー**:
- **API.ts 232-234行目**: テキスト型チェック
- **API.ts 236行目**: パッド取得
- **API.ts 237行目**: pad.appendText呼び出し
- **API.ts 238行目**: updatePadClients呼び出し
- **Pad.ts 321行目**: spliceText(text().length - 1, 0, newText, authorId)

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

```
PATCH /api/2/pads/text (RestAPI.ts)
    │
    └─ apiHandler.handle() (APIHandler.ts)
           │
           └─ api.appendText() (API.ts:230-239)
                  │
                  ├─ テキスト型チェック (232-234行目)
                  │
                  ├─ getPadSafe(padID, true) (236行目)
                  │
                  ├─ pad.appendText(text, authorId) (237行目)
                  │      │
                  │      └─ pad.spliceText(text().length - 1, 0, text, authorId)
                  │             │
                  │             ├─ cleanText(ins) (290行目)
                  │             ├─ makeSplice() (297行目)
                  │             └─ appendRevision(changeset, authorId) (298行目)
                  │
                  └─ padMessageHandler.updatePadClients(pad) (238行目)
```

### データフロー図

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

padID         ───────▶ getPadSafe() ──────────────▶ Padオブジェクト
text                         │
authorId                     ▼
                    pad.appendText()
                         │
                         ├─▶ text().length - 1（挿入位置計算）
                         │
                         └─▶ spliceText(pos, 0, text)
                                │
                                ├─▶ cleanText()
                                │
                                ├─▶ makeSplice()
                                │
                                └─▶ appendRevision()
                                       │
                                       ├─▶ pad:{padID}:revs:{n} [INSERT]
                                       │
                                       └─▶ pad:{padID} [UPDATE]
                                       │
                                       ▼
                    updatePadClients()
                         │
                         └─▶ Socket.IO通知 ───────▶ 接続クライアント
                                │
                                ▼
                    {code: 0, message: "ok"} ─▶ JSON Response
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| API.ts | `src/node/db/API.ts` | ソース | appendText関数の実装 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | Padクラス、appendText/spliceText |
| RestAPI.ts | `src/node/handler/RestAPI.ts` | ソース | REST APIエンドポイント定義 |
| APIHandler.ts | `src/node/handler/APIHandler.ts` | ソース | API呼び出しハンドリング |
| PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | ソース | クライアント通知 |
| Changeset.ts | `src/static/js/Changeset.ts` | ソース | makeSplice関数 |
| DB.ts | `src/node/db/DB.ts` | ソース | データベースアクセス層 |
