# 機能設計書 11-パッド移動

## 概要

本ドキュメントは、Etherpadにおけるパッド移動機能（movePad）の設計仕様を定義する。この機能は、既存のパッドを別のパッドIDに移動（リネーム）するためのAPIを提供する。

### 本機能の処理概要

パッド移動機能は、既存のパッドのIDを変更し、新しいIDでアクセスできるようにする機能である。内部的には、元のパッドを新しいIDにコピーした後、元のパッドを削除することで実現している。

**業務上の目的・背景**：
パッドの作成後に命名規則の変更やプロジェクト構成の見直しが発生した場合、パッドのIDを変更する必要がある。手動でコピー・削除を行うと履歴やメタデータの管理が煩雑になるため、一括して移動できる機能が必要である。また、グループ間でパッドを移動させることで、アクセス権限の管理を柔軟に行うことができる。

**機能の利用シーン**：
- プロジェクト名の変更に伴うパッドIDの変更
- グループパッドを別のグループに移動
- パッドの整理・再構成
- 命名規則の統一化作業

**主要な処理内容**：
1. 移動元パッドの存在確認とバリデーション
2. 移動先パッドIDの形式チェック
3. 移動先パッドが既に存在する場合の処理（forceオプション）
4. パッドデータの完全コピー（リビジョン履歴、チャット履歴含む）
5. 著者情報の移動先パッドへの関連付け
6. グループパッドの場合のグループ登録処理
7. 元パッドの削除（接続中クライアントのキック含む）

**関連システム・外部連携**：
- REST API（POST /api/2/pads/movePad）を通じて外部システムから呼び出し可能
- Socket.IO経由で接続中のクライアントに影響（移動元パッドからキックアウト）

**権限による制御**：
- APIキーまたはOAuth2トークンによる認証が必要
- パッドの移動操作にはAPI認証権限が必要

## 関連画面

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

## 機能種別

CRUD操作（移動 = コピー + 削除の複合操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| sourceID | string | Yes | 移動元のパッドID | 正規表現 `^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$` に一致、存在するパッドであること |
| destinationID | string | Yes | 移動先のパッドID | 正規表現 `^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$` に一致 |
| force | boolean | No | 移動先パッドが存在する場合に上書きするかどうか（デフォルト: false） | true/false、または文字列"true"/"false" |

### 入力データソース

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

## 出力仕様

### 出力データ

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

### 出力先

HTTPレスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ POST /api/2/pads/movePad
2. 認証チェック
   └─ APIキーまたはOAuth2トークンの検証
3. パラメータ抽出
   └─ sourceID, destinationID, forceの取得
4. 移動元パッド取得
   └─ getPadSafe(sourceID, true)でパッドオブジェクト取得
5. パッドコピー処理
   └─ pad.copy(destinationID, force)
   └─ グループ存在確認、強制上書き処理、データコピー実行
6. 元パッド削除
   └─ pad.remove()
   └─ 接続クライアントのキック、関連データの削除
7. レスポンス返却
   └─ {code: 0, message: "ok", data: {padID: destinationID}}
```

### フローチャート

```mermaid
flowchart TD
    A[開始: movePad API呼び出し] --> B{認証チェック}
    B -->|失敗| C[401 Unauthorized]
    B -->|成功| D[sourceIDでパッド取得]
    D -->|存在しない| E[400 padID does not exist]
    D -->|存在する| F[コピー処理開始]
    F --> G{destinationIDにグループIDあり?}
    G -->|Yes| H{グループ存在?}
    H -->|No| I[400 groupID does not exist]
    H -->|Yes| J[処理続行]
    G -->|No| J
    J --> K{destinationIDが既に存在?}
    K -->|Yes| L{force=true?}
    L -->|No| M[400 destinationID already exists]
    L -->|Yes| N[既存パッド削除]
    N --> O[パッドデータコピー]
    K -->|No| O
    O --> P[著者情報の関連付け]
    P --> Q{グループパッド?}
    Q -->|Yes| R[グループにパッド登録]
    Q -->|No| S[元パッド削除]
    R --> S
    S --> T[接続クライアントをキック]
    T --> U[関連データ削除]
    U --> V[200 OK: padID返却]
    V --> W[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-011-01 | 移動元存在確認 | 移動元のパッドが存在しない場合はエラーを返す | 常時 |
| BR-011-02 | 移動先重複チェック | 移動先パッドIDが既に存在し、forceがfalseの場合はエラーを返す | 移動先パッドが存在する場合 |
| BR-011-03 | グループ存在確認 | 移動先がグループパッド形式の場合、該当グループが存在することを確認 | 移動先IDに`$`が含まれる場合 |
| BR-011-04 | 完全データ移動 | リビジョン履歴、チャット履歴を含む全データを移動 | 常時 |
| BR-011-05 | 著者情報継承 | 元パッドに貢献した著者情報を移動先パッドにも関連付け | 常時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| コピー | pad:{destinationID} | INSERT | パッドメタデータのコピー |
| コピー | pad:{destinationID}:revs:{n} | INSERT | 全リビジョンデータのコピー |
| コピー | pad:{destinationID}:chat:{n} | INSERT | 全チャットメッセージのコピー |
| 登録 | group:{groupID} | UPDATE | グループパッドの場合、グループ登録 |
| 登録 | globalAuthor:{authorID} | UPDATE | 著者へのパッド関連付け追加 |
| 削除 | pad:{sourceID} | DELETE | 元パッドメタデータの削除 |
| 削除 | pad:{sourceID}:revs:{n} | DELETE | 元パッドの全リビジョン削除 |
| 削除 | pad:{sourceID}:chat:{n} | DELETE | 元パッドの全チャット削除 |
| 削除 | pad2readonly:{sourceID} | DELETE | ReadOnly関連削除 |
| 削除 | readonly2pad:{readonlyID} | DELETE | ReadOnly逆引き削除 |
| 削除 | group:{groupID} | UPDATE | 元グループからパッド登録解除 |
| 削除 | globalAuthor:{authorID} | UPDATE | 著者からのパッド関連付け削除 |

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

#### pad:{destinationID}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | atext | 元パッドのatext | テキストと属性 |
| INSERT | pool | 元パッドのpool | 属性プール |
| INSERT | head | 元パッドのhead | 最新リビジョン番号 |
| INSERT | chatHead | 元パッドのchatHead | 最新チャット番号 |
| INSERT | publicStatus | 元パッドのpublicStatus | 公開ステータス |
| INSERT | savedRevisions | 元パッドのsavedRevisions | 保存済みリビジョン |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 1 | apierror | 移動元パッドが存在しない | 正しいパッドIDを指定 |
| 1 | apierror | 移動先パッドが既に存在（force=false） | forceをtrueに設定するか別のIDを指定 |
| 1 | apierror | 移動先グループが存在しない | 先にグループを作成 |
| 1 | apierror | パッドIDの形式が不正 | 正しい形式のIDを指定 |
| 4 | authError | 認証情報が無効 | 正しいAPIキーまたはトークンを使用 |

### リトライ仕様

データベース操作に関してはueberDB2のリトライ機構に依存。API層でのリトライは行わない。

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

コピー処理と削除処理は個別に実行される。途中で失敗した場合、以下の状態となる可能性がある：
- コピー成功後に削除失敗：両方のパッドが存在する状態
- 部分的なコピー失敗：不完全なコピー先パッドが残る可能性

ueberDB2のバッチ処理により、一定のデータ整合性は確保されるが、完全なトランザクション保証はない。

## パフォーマンス要件

- 大量のリビジョンを持つパッドの場合、コピー処理に時間がかかる
- リビジョン・チャットのコピーは100件単位でバッチ処理される
- 非同期処理により、API応答はコピー完了まで待機

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

- APIキーまたはOAuth2認証が必須
- パッドIDのサニタイズ処理により、パストラバーサル攻撃を防止
- 移動元パッドへの接続クライアントは強制切断される

## 備考

- 移動処理は内部的にコピー + 削除として実装されている
- ReadOnlyIDは移動先パッドで新規生成される（元のReadOnlyIDは無効化）
- 移動中に他のクライアントが編集を行うと、データ不整合の可能性あり

---

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

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

### 推奨読解順序

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

パッドの移動処理を理解するには、まずパッドオブジェクトの構造を把握することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Pad.ts | `src/node/db/Pad.ts` | Padクラスの構造、atext/pool/head等のプロパティ定義 |
| 1-2 | PadType.ts | `src/node/types/PadType.ts` | パッドの型定義 |

**読解のコツ**: Padクラスのコンストラクタ（58-67行目）でパッドの初期状態を確認し、各プロパティの役割を理解する。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RestAPI.ts | `src/node/handler/RestAPI.ts` | API定義とルーティング（1064-1090行目でmovePadのマッピング） |
| 2-2 | APIHandler.ts | `src/node/handler/APIHandler.ts` | パラメータ抽出と関数呼び出し（98-99行目でmovePadの引数定義） |

**主要処理フロー**:
1. **1064-1090行目**: POST /pads/movePad のルート定義
2. **1459-1466行目**: マッピングに基づくAPI関数呼び出し

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

実際の移動処理ロジックを読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | API.ts | `src/node/db/API.ts` | movePad関数の定義（650-654行目） |
| 3-2 | Pad.ts | `src/node/db/Pad.ts` | copy関数（403-454行目）とremove関数（557-615行目） |
| 3-3 | PadManager.ts | `src/node/db/PadManager.ts` | パッド存在確認、取得、削除処理 |

**主要処理フロー**:
- **API.ts 650-654行目**: movePad関数 - パッド取得、コピー、削除の順で実行
- **Pad.ts 403-410行目**: copy関数開始 - パッドのフラッシュ
- **Pad.ts 413-433行目**: コピー処理 - レコードのコピー
- **Pad.ts 557-615行目**: remove関数 - 削除処理

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

```
POST /api/2/pads/movePad (RestAPI.ts)
    │
    └─ apiHandler.handle() (APIHandler.ts)
           │
           └─ api.movePad() (API.ts:650-654)
                  │
                  ├─ getPadSafe(sourceID, true) (API.ts:651)
                  │      └─ padManager.getPad() (PadManager.ts:109-144)
                  │
                  ├─ pad.copy(destinationID, force) (Pad.ts:403-454)
                  │      ├─ checkIfGroupExistAndReturnIt() (Pad.ts:456-469)
                  │      ├─ removePadIfForceIsTrueAndAlreadyExist() (Pad.ts:471-492)
                  │      ├─ copyRecord() (Pad.ts:418-421) [リビジョン・チャット]
                  │      ├─ copyAuthorInfoToDestinationPad() (Pad.ts:494-498)
                  │      └─ padManager.getPad(destinationID) (PadManager.ts)
                  │
                  └─ pad.remove() (Pad.ts:557-615)
                         ├─ padMessageHandler.kickSessionsFromPad()
                         ├─ db.remove() [各種データ]
                         ├─ authorManager.removePad()
                         └─ padManager.removePad()
```

### データフロー図

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

sourceID      ───────▶ getPadSafe() ──────────────▶ Padオブジェクト
destinationID          │
force                  ▼
                  pad.copy()
                       │
                       ├─▶ pad:{destID} [INSERT]
                       ├─▶ pad:{destID}:revs:* [INSERT]
                       ├─▶ pad:{destID}:chat:* [INSERT]
                       ├─▶ group:{groupID} [UPDATE]
                       └─▶ globalAuthor:* [UPDATE]
                       │
                       ▼
                  pad.remove()
                       │
                       ├─▶ pad:{srcID} [DELETE]
                       ├─▶ pad:{srcID}:revs:* [DELETE]
                       ├─▶ pad:{srcID}:chat:* [DELETE]
                       ├─▶ pad2readonly:{srcID} [DELETE]
                       └─▶ readonly2pad:{roID} [DELETE]
                       │
                       ▼
                  {padID: destinationID} ───────▶ JSON Response
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| API.ts | `src/node/db/API.ts` | ソース | movePad関数の実装 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | Padクラス、copy/remove実装 |
| PadManager.ts | `src/node/db/PadManager.ts` | ソース | パッドの取得・存在確認・削除 |
| RestAPI.ts | `src/node/handler/RestAPI.ts` | ソース | REST APIエンドポイント定義 |
| APIHandler.ts | `src/node/handler/APIHandler.ts` | ソース | API呼び出しハンドリング |
| ReadOnlyManager.ts | `src/node/db/ReadOnlyManager.ts` | ソース | ReadOnlyID管理 |
| GroupManager.ts | `src/node/db/GroupManager.ts` | ソース | グループ存在確認 |
| AuthorManager.ts | `src/node/db/AuthorManager.ts` | ソース | 著者情報管理 |
| PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | ソース | クライアントセッション管理 |
| DB.ts | `src/node/db/DB.ts` | ソース | データベースアクセス層 |
