# 機能設計書 32-リビジョン復元

## 概要

本ドキュメントは、Etherpadのリビジョン復元機能（restoreRevision）の設計仕様を記載する。

### 本機能の処理概要

本機能は、指定されたパッドの過去のリビジョンを現在のパッドに復元するAPI機能である。復元は新しいリビジョンとして追加されるため、履歴は保持される。

**業務上の目的・背景**：共同編集において、誤った編集や意図しない変更が行われた場合に、過去の状態に戻す必要が生じる。この機能は、パッドの任意のリビジョンを現在の状態として復元することで、編集ミスからの回復を可能にする。従来の手動コピー&ペーストと異なり、API経由で自動化された復元処理を提供する。

**機能の利用シーン**：誤った編集を取り消したい場合、重要な節目のバージョンに戻したい場合、自動バックアップシステムからの復元処理、タイムスライダーからの「このバージョンを復元」操作など。

**主要な処理内容**：
1. パッドIDとリビジョン番号の検証
2. 指定リビジョンのattributed text（属性付きテキスト）を取得
3. 現在のテキストから指定リビジョンへの変更セットを構築
4. 変更セットを新しいリビジョンとして適用
5. 接続中のクライアントに更新を通知

**関連システム・外部連携**：REST API経由でHTTPリクエストを受け付ける。Socket.IOを通じて接続中のクライアントに変更を通知する。データベース（ueberDB2）にリビジョンデータを保存する。

**権限による制御**：APIキー認証またはSSO認証により、API呼び出しの認可を行う。読み取り専用アクセスのユーザーはこの機能を利用できない。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | パッド編集画面 | 参照画面 | エディタから復元操作が可能 |
| 3 | タイムスライダー画面 | 主画面 | 特定リビジョンを選択して復元 |

## 機能種別

データ更新（UPDATE操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| padID | string | Yes | 対象パッドのID | 文字列型であること、パッドが存在すること |
| rev | integer | Yes | 復元対象のリビジョン番号 | 整数型であること、0以上かつhead以下であること |
| authorId | string | No | 操作を行う著者のID | 空文字列がデフォルト |

### 入力データソース

REST API PATCHリクエストのボディとしてJSON形式でパラメータを受け取る。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| code | integer | 結果コード（0: 成功） |
| message | string | 結果メッセージ（"ok"） |
| data | null | 正常終了時はnull |

### 出力先

HTTPレスポンスとしてJSON形式で返却。また、Socket.IO経由で接続中クライアントに変更を通知。

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ PATCH /api/2/savedRevisions
2. 認証・認可チェック
   └─ APIキーまたはSSOトークンの検証
3. パラメータ検証
   └─ revが定義されているか、数値として有効か確認
4. パッド存在確認・取得
   └─ padManager.getPadを呼び出し
5. リビジョン範囲検証
   └─ revがhead以下であることを確認
6. 指定リビジョンのatextを取得
   └─ pad.getInternalRevisionAText(rev)を呼び出し
7. 変更セット構築
   └─ Builderを使用して現在のテキストからatextへの変更セットを作成
8. リビジョン追加
   └─ pad.appendRevision(changeset, authorId)を呼び出し
9. クライアント更新通知
   └─ padMessageHandler.updatePadClients(pad)を呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[APIリクエスト受信] --> B{認証チェック}
    B -->|失敗| C[401 Unauthorized]
    B -->|成功| D{rev定義確認}
    D -->|未定義| E[rev is not defined]
    D -->|定義済| F{rev数値検証}
    F -->|不正| G[rev is not a number]
    F -->|正常| H{パッド存在確認}
    H -->|存在しない| I[padID does not exist]
    H -->|存在する| J{rev範囲確認}
    J -->|head超過| K[rev is higher than head]
    J -->|正常| L[指定リビジョンのatext取得]
    L --> M[変更セット構築]
    M --> N[appendRevision実行]
    N --> O[クライアント更新通知]
    O --> P[JSONレスポンス返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-32-01 | パッド存在必須 | 指定されたパッドIDが存在しない場合はエラー | 常時 |
| BR-32-02 | リビジョン必須 | revパラメータは必須 | 常時 |
| BR-32-03 | リビジョン範囲制限 | revは0以上かつhead以下である必要がある | 常時 |
| BR-32-04 | 非破壊的復元 | 復元は新しいリビジョンとして追加され、履歴は保持される | 常時 |
| BR-32-05 | リアルタイム通知 | 復元後、接続中の全クライアントに変更が通知される | 常時 |

### 計算ロジック

変更セットの構築は以下のロジックで行われる：
1. 現在のテキスト(oldText)と復元先のatext.textを取得
2. Builderオブジェクトを生成（oldText.lengthを初期値として）
3. atextの属性ランごとにinsert操作を実行
4. 現在テキストの削除操作（remove）を構築
5. 最終的な変更セットを生成

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| リビジョン取得 | pad:{padID}:revs:{rev} | SELECT | 指定リビジョンのデータを取得 |
| リビジョン追加 | pad:{padID}:revs:{newRev} | INSERT | 新しいリビジョンを保存 |
| パッド更新 | pad:{padID} | UPDATE | パッドのメタ情報を更新 |

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

#### pad:{padID}:revs:{rev}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | changeset, meta | キー: pad:{padID}:revs:{rev} | 復元元リビジョンの取得 |

#### pad:{padID}:revs:{newRev}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | changeset, meta | 新規リビジョンデータ | 復元結果の保存 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 1 | apierror | revが未定義 | revパラメータを指定 |
| 1 | apierror | revが数値でない | 有効な整数を指定 |
| 1 | apierror | パッドが存在しない | 存在するパッドIDを指定 |
| 1 | apierror | revがhead超過 | 有効なリビジョン番号を指定 |
| 4 | 認証エラー | APIキーが無効 | 正しいAPIキーを使用 |

### リトライ仕様

復元操作は冪等ではないため、失敗時の自動リトライは推奨されない。エラー原因を確認後、手動でリトライする。

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

リビジョン追加はアトミックに行われる。appendRevision関数内でパッドの更新とリビジョンデータの保存が行われる。

## パフォーマンス要件

- レスポンス時間: 500ms以内（通常条件下）
- 大きなパッド（多数のリビジョン）では、atext取得に時間がかかる場合がある

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

- APIキー認証またはSSO認証が必須
- authorIdパラメータにより、復元操作の実行者を記録
- 読み取り専用セッションでは復元不可

## 備考

API Version 1.3.0で追加された機能。復元は非破壊的であり、元のリビジョン履歴は保持される。

---

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

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

### 推奨読解順序

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

まず、Changesetとatextの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Changeset.ts | `src/static/js/Changeset.ts` | 変更セットの構造と操作 |
| 1-2 | Builder.ts | `src/static/js/Builder.ts` | 変更セットを構築するBuilderクラス |

**読解のコツ**: Changesetは「Z:oldLen>deltaLen|lines+chars$text」形式の文字列で表現される。Builderはこの文字列を構築するヘルパークラス。

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

REST APIのエンドポイント定義を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | RestAPI.ts | `src/node/handler/RestAPI.ts` | PATCH /savedRevisionsのマッピング定義（1334-1360行目） |

**主要処理フロー**:
1. **1334-1360行目**: エンドポイント定義とrestoreRevision関数へのマッピング
2. **1434-1526行目**: リクエスト処理とAPIハンドラー呼び出し

#### Step 3: API関数の実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | API.ts | `src/node/db/API.ts` | restoreRevision関数の実装（548-601行目） |

**主要処理フロー**:
- **548-553行目**: revパラメータの検証
- **556-561行目**: パッド取得とリビジョン範囲確認
- **563-566行目**: 指定リビジョンのatext取得
- **568-579行目**: 属性ランの処理関数
- **582-596行目**: 変更セットの構築
- **599-600行目**: リビジョン追加とクライアント通知

#### Step 4: パッド更新処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Pad.ts | `src/node/db/Pad.ts` | appendRevision()メソッド（97-144行目） |
| 4-2 | PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | updatePadClients()関数（738-795行目） |

**主要処理フロー**:
- **97-144行目**: 変更セットの適用とデータベース保存
- **738-795行目**: 接続中クライアントへの更新通知

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

```
RestAPI.expressCreateServer
    │
    ├─ app.use('/api/2', ...)
    │      └─ apiHandler.handle('1.3.0', 'restoreRevision', fields)
    │              └─ API.restoreRevision(padID, rev, authorId)
    │                      ├─ checkValidRev(rev)
    │                      ├─ getPadSafe(padID, true)
    │                      ├─ pad.getInternalRevisionAText(rev)
    │                      ├─ Builder.insert() / Builder.remove()
    │                      ├─ pad.appendRevision(changeset, authorId)
    │                      └─ padMessageHandler.updatePadClients(pad)
    │
    └─ res.json(response)
```

### データフロー図

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

padID, rev, authorId ──▶ getInternalRevisionAText() ──▶ atext
                                   │
                                   ▼
                          Builder構築
                                   │
                                   ▼
oldText ─────────────────▶ changeset生成 ───────────▶ appendRevision()
                                   │
                                   ▼
                          updatePadClients() ────────▶ Socket.IO通知
                                   │
                                   ▼
                          JSON Response ─────────────▶ HTTPレスポンス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| API.ts | `src/node/db/API.ts` | ソース | restoreRevision関数の実装 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | Padクラスとリビジョン管理 |
| RestAPI.ts | `src/node/handler/RestAPI.ts` | ソース | REST APIエンドポイント定義 |
| PadMessageHandler.ts | `src/node/handler/PadMessageHandler.ts` | ソース | クライアント通知処理 |
| Builder.ts | `src/static/js/Builder.ts` | ソース | Changeset構築ヘルパー |
| Changeset.ts | `src/static/js/Changeset.ts` | ソース | Changeset操作関数群 |
| checkValidRev.ts | `src/node/utils/checkValidRev.ts` | ソース | リビジョン番号の検証 |
