# 機能設計書 54-Etherpadエクスポート

## 概要

本ドキュメントは、EtherpadにおけるEtherpad形式エクスポート機能の設計仕様を記載する。パッドの完全なデータ（リビジョン履歴、チャット履歴、著者情報を含む）をJSON形式でエクスポートする機能について詳細に解説する。

### 本機能の処理概要

Etherpad形式エクスポート機能は、パッドのすべてのデータを完全にエクスポートし、別のEtherpadインスタンスで復元可能な形式で出力する機能を提供する。これには現在のテキスト内容だけでなく、すべてのリビジョン履歴、チャットメッセージ、著者情報が含まれる。

**業務上の目的・背景**：
パッドの完全なバックアップや、別のEtherpadサーバーへの移行が必要な場合がある。通常のテキストやHTMLエクスポートでは履歴情報が失われるが、Etherpad形式では編集履歴を含む完全なデータを保持できる。これにより、タイムスライダーでの履歴閲覧やバージョン比較機能も移行先で利用可能になる。

**機能の利用シーン**：
- パッドの完全バックアップを作成する場合
- 別のEtherpadインスタンスにパッドを移行する場合
- 開発・テスト環境間でパッドデータを移動する場合
- 監査目的で編集履歴を含めてアーカイブする場合

**主要な処理内容**：
1. エクスポートリクエストを受信（GET /p/:pad/export/etherpad）
2. パッドのメインデータを取得
3. すべてのリビジョンデータを取得
4. すべてのチャットメッセージを取得
5. 関連する著者情報を取得
6. プラグインの追加データを取得（フック経由）
7. JSONとしてシリアライズして返却

**関連システム・外部連携**：
- プラグインフック: exportEtherpadAdditionalContent, exportEtherpad

**権限による制御**：
パッドへの読み取りアクセス権があれば実行可能。読み取り専用パッドからもエクスポート可能。出力時は読み取り専用IDを使用してパッドIDを隠蔽可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 2 | パッド編集画面 | 補助機能 | エクスポートポップアップからetherpad形式でダウンロード |
| 3 | タイムスライダー画面 | 補助機能 | エクスポートポップアップからetherpad形式でダウンロード |

## 機能種別

帳票出力 / データエクスポート / 完全バックアップ

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| pad | string | Yes | URLパスパラメータとして指定されるパッドID | 存在するパッドID |

### 入力データソース

- HTTPリクエスト（GET /p/:pad/export/etherpad）
- パッドデータベース（複数キー）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| pad:{padId} | object | パッドのメインデータ（atext、pool、head等） |
| pad:{padId}:revs:{n} | object | 各リビジョンのデータ（changeset、meta） |
| pad:{padId}:chat:{n} | object | 各チャットメッセージ |
| globalAuthor:{authorId} | object | 著者情報（name、colorId、padIDs） |
| {customPrefix}:{padId} | object | プラグインによる追加データ |

### 出力先

- HTTPレスポンス（application/json形式のダウンロードファイル）
- Content-Disposition: attachment; filename="{padId}.etherpad"

## 処理フロー

### 処理シーケンス

```
1. HTTPリクエスト受信（GET /p/:pad/export/etherpad）
   └─ express-rate-limitによるレート制限チェック
2. パッドアクセス権限検証
   └─ hasPadAccessでセッション・トークン検証
3. パッドID解決
   └─ 読み取り専用IDの場合は実パッドIDを取得（出力はreadOnlyId使用）
4. パッド存在確認
   └─ padManager.doesPadExistsで確認
5. データ収集
   ├─ パッドメインデータ取得
   ├─ 全リビジョンデータ取得（0〜head）
   ├─ 全チャットメッセージ取得（0〜chatHead）
   ├─ 著者情報取得（getAllAuthors経由）
   └─ プラグイン追加データ取得（exportEtherpadAdditionalContentフック）
6. プラグインフック呼び出し
   └─ exportEtherpadで最終調整
7. レスポンス送信
   └─ res.attachmentでファイル名設定、res.sendでJSON送信
```

### フローチャート

```mermaid
flowchart TD
    A[エクスポートリクエスト受信] --> B{レート制限チェック}
    B -->|制限内| C{アクセス権限チェック}
    B -->|制限超過| Z1[429エラー]
    C -->|許可| D{読み取り専用ID?}
    C -->|拒否| Z2[403エラー]
    D -->|Yes| E[実パッドID取得・出力用にreadOnlyId保持]
    D -->|No| F{パッド存在確認}
    E --> F
    F -->|存在| G[exportEtherpad.getPadRaw]
    F -->|不存在| Z3[404エラー]
    G --> H[パッドメインデータ取得]
    H --> I[著者情報収集]
    I --> J[リビジョンデータ収集]
    J --> K[チャットデータ収集]
    K --> L[プラグイン追加データ収集]
    L --> M[exportEtherpadフック実行]
    M --> N[res.attachment設定]
    N --> O[JSONレスポンス送信]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-54-01 | パッドID変換 | 読み取り専用IDでアクセス時は出力データ内のパッドIDをreadOnlyIdに変換 | readOnlyId使用時 |
| BR-54-02 | 著者padIDs変換 | globalAuthor.padIDsをエクスポート対象パッドIDの単一文字列に変換 | 常時 |
| BR-54-03 | 全リビジョン出力 | rev 0からheadまでのすべてのリビジョンを出力 | 常時 |
| BR-54-04 | 全チャット出力 | chat 0からchatHeadまでのすべてのメッセージを出力 | 常時 |

### 計算ロジック

データ収集の並列化:
- リビジョンとチャットメッセージはジェネレータとStreamで非同期に収集
- 100件ずつバッチ処理でDBアクセスを最適化

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| パッドデータ取得 | pad:{padId} | SELECT | パッドのメインデータを取得 |
| リビジョンデータ取得 | pad:{padId}:revs:{n} | SELECT | 全リビジョンを取得 |
| チャットデータ取得 | pad:{padId}:chat:{n} | SELECT | 全チャットメッセージを取得 |
| 著者データ取得 | globalAuthor:{authorId} | SELECT | 関連著者情報を取得 |
| プラグインデータ取得 | {customPrefix}:{padId}:* | SELECT | プラグイン追加データを取得 |

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

#### pad:{padId}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | 全フィールド | パッドオブジェクト全体 | JSON変換してそのまま出力 |

#### globalAuthor:{authorId}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | name, colorId, padIDs | 著者情報 | padIDsは単一パッドIDに変換 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 404 | Not Found | パッドが存在しない | 正しいパッドIDを指定 |
| 403 | Forbidden | アクセス権限がない | 適切な認証を行う |
| 429 | Too Many Requests | レート制限超過 | 時間をおいて再試行 |

### リトライ仕様

自動リトライは実装されていない。クライアント側で手動リトライが必要。

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

読み取り専用操作のためトランザクション管理は不要。ただし、エクスポート中にパッドが更新されると不整合が生じる可能性がある（スナップショットではない）。

## パフォーマンス要件

- 大量のリビジョンを持つパッドでは処理時間が長くなる
- 100件ずつのバッチ処理でメモリ使用量を抑制
- レート制限により過負荷を防止

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

- レート制限によるDoS攻撃対策
- hasPadAccessによる認証・認可チェック
- 読み取り専用IDでアクセス時は実パッドIDを出力に含めない
- 著者情報に含まれる他パッドの情報はエクスポート対象パッドIDのみに制限
- CORSヘッダー設定（Access-Control-Allow-Origin: *）

## 備考

- エクスポートデータはインポート時にパッドIDを変更可能
- プラグインはexportEtherpadAdditionalContentフックで追加データを登録可能
- Streamクラスでジェネレータからの非同期データ収集を効率化

---

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

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

### 推奨読解順序

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

エクスポートされるデータの構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PadType.ts | `src/node/types/PadType.ts` | Padクラスのプロパティ構造 |
| 1-2 | Pad.ts | `src/node/db/Pad.ts` | head、chatHead、getAllAuthorsなどのメソッド |

**読解のコツ**: Padオブジェクトはatext、pool、head（最新リビジョン番号）、chatHead（最新チャット番号）を持つ。これらがエクスポートのキーデータ。

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

HTTPリクエストのルーティングからエクスポートハンドラへの流れを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | importexport.ts | `src/node/hooks/express/importexport.ts` | GETルーティング設定 |

**主要処理フロー**:
1. **31行目**: 対応形式にetherpadが含まれる
2. **68行目**: exportHandler.doExportの呼び出し

#### Step 3: エクスポートハンドラを理解する

形式別の分岐とetherpad形式の処理を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ExportHandler.ts | `src/node/handler/ExportHandler.ts` | メインエクスポート処理 |

**主要処理フロー**:
- **69-71行目**: etherpad形式の場合の処理
- **70行目**: exportEtherpad.getPadRawの呼び出し
- **71行目**: res.sendでJSON送信

#### Step 4: Etherpadエクスポートユーティリティを理解する

実際のデータ収集・変換ロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ExportEtherpad.ts | `src/node/utils/ExportEtherpad.ts` | Etherpad形式エクスポート処理 |

**主要処理フロー**:
- **24行目**: getPadRaw関数の開始
- **25行目**: dstPfxの決定（readOnlyId優先）
- **26-29行目**: パッドとプラグインプレフィックス取得
- **30-42行目**: プラグイン追加データの収集ジェネレータ
- **43-55行目**: メインデータ収集ジェネレータ
  - **44-50行目**: 著者情報（padIDsを単一文字列に変換）
  - **52行目**: リビジョンデータ（0〜head）
  - **53行目**: チャットデータ（0〜chatHead）
- **56-57行目**: Streamでバッチ処理
- **58-63行目**: exportEtherpadフック呼び出し

**読解のコツ**: ジェネレータ関数（function*）でデータを遅延生成し、Streamクラスで100件ずつバッチ処理している。これによりメモリ効率を向上。

#### Step 5: Streamユーティリティを理解する

非同期データ収集のヘルパーを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | Stream.ts | `src/node/utils/Stream.ts` | Streamクラスの実装 |

**読解のコツ**: batch()でN件ずつグループ化、buffer()で非同期処理のバッファリングを行う。

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

```
expressCreateServer (importexport.ts)
    │
    └─ GET /p/:pad/export/etherpad
           │
           ├─ hasPadAccess
           │
           ├─ readOnlyManager.isReadOnlyId
           │      └─ readOnlyManager.getPadId
           │
           ├─ padManager.doesPadExists
           │
           └─ exportHandler.doExport (ExportHandler.ts)
                  │
                  └─ exportEtherpad.getPadRaw (ExportEtherpad.ts)
                         │
                         ├─ padManager.getPad
                         │
                         ├─ hooks.aCallAll('exportEtherpadAdditionalContent')
                         │
                         ├─ [Generator] 著者情報収集
                         │      └─ authorManager.getAuthor
                         │
                         ├─ [Generator] リビジョン収集
                         │      └─ pad.getRevision (0〜head)
                         │
                         ├─ [Generator] チャット収集
                         │      └─ pad.getChatMessage (0〜chatHead)
                         │
                         ├─ [Generator] プラグインデータ収集
                         │      └─ pad.db.findKeys / pad.db.get
                         │
                         ├─ Stream.batch(100).buffer(99)
                         │
                         └─ hooks.aCallAll('exportEtherpad')
```

### データフロー図

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

HTTPリクエスト       ┌─────────────────┐
GET /p/:pad/        │  ルーティング    │
export/etherpad ───▶│  importexport   │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  権限チェック    │
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────────────────────────┐
                    │  getPadRaw (ExportEtherpad.ts)      │
                    │                                     │
パッドDB ─────────▶ │  ┌────────────┐  ┌────────────┐   │
pad:{padId}         │  │ パッド本体 │  │ リビジョン │   │
                    │  └────────────┘  └────────────┘   │
                    │                                     │
pad:*:revs:* ─────▶ │  ┌────────────┐  ┌────────────┐   │
                    │  │ チャット   │  │ 著者情報   │   │
pad:*:chat:* ─────▶ │  └────────────┘  └────────────┘   │
                    │                                     │
globalAuthor:* ───▶ │  ┌────────────────────────────┐   │
                    │  │ プラグイン追加データ       │   │
{custom}:* ───────▶ │  └────────────────────────────┘   │
                    │                                     │
                    └────────────────┬────────────────────┘
                                     │
                                     ▼
                    ┌─────────────────┐
                    │  JSON シリアライズ│
                    └────────┬────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │  res.send       │ ───────▶ .etherpadファイル
                    │  JSON返却       │          ダウンロード
                    └─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| importexport.ts | `src/node/hooks/express/importexport.ts` | ソース | Expressルーティング定義 |
| ExportHandler.ts | `src/node/handler/ExportHandler.ts` | ソース | エクスポートハンドラ |
| ExportEtherpad.ts | `src/node/utils/ExportEtherpad.ts` | ソース | Etherpad形式エクスポート処理 |
| Stream.ts | `src/node/utils/Stream.ts` | ソース | 非同期ストリーム処理 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | パッドモデル |
| AuthorManager.ts | `src/node/db/AuthorManager.ts` | ソース | 著者情報管理 |
| PadManager.ts | `src/node/db/PadManager.ts` | ソース | パッド管理 |
| ReadOnlyManager.ts | `src/node/db/ReadOnlyManager.ts` | ソース | 読み取り専用ID管理 |
