# 機能設計書 63-パッド管理

## 概要

本ドキュメントは、Etherpadのパッド管理機能の設計を記載する。この機能により、管理者はWebインターフェースを通じてパッドの検索、一覧表示、削除、およびリビジョンクリーンアップを行うことができる。

### 本機能の処理概要

パッド管理機能は、Etherpad上に存在するすべてのパッドを管理者が一元的に管理するための機能である。パッドの検索、ソート、削除、およびストレージ節約のためのリビジョンクリーンアップを提供する。

**業務上の目的・背景**：大規模なEtherpad環境では数千のパッドが存在する可能性がある。管理者がこれらのパッドを効率的に管理・監視するためには、検索、ソート、削除などの管理機能が不可欠である。また、長期間使用されたパッドはリビジョン数が増大しストレージを圧迫するため、クリーンアップ機能も重要である。

**機能の利用シーン**：
- 特定のパッドを検索して確認する場合
- 不要なパッドを削除する場合
- アクティブユーザー数でパッドをソートして利用状況を確認する場合
- リビジョン数が多いパッドをクリーンアップする場合
- 新規パッドを管理画面から作成する場合

**主要な処理内容**：
1. 全パッドの一覧取得と検索フィルタリング
2. パッド名、ユーザー数、最終編集日時、リビジョン数でのソート
3. パッドの削除
4. パッドの新規作成
5. リビジョンのクリーンアップ

**関連システム・外部連携**：
- データベース（パッド情報の取得・削除）
- PadMessageHandler（アクティブユーザー数取得）

**権限による制御**：管理者権限（is_admin: true）を持つユーザーのみがパッド管理機能にアクセスできる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | パッド管理画面 | 主画面 | パッドの検索・一覧表示・削除・リビジョンクリーンアップ |

## 機能種別

CRUD操作 / 検索機能

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| pattern | string | No | 検索パターン（パッド名部分一致） | - |
| sortBy | string | No | ソート基準（padName/userCount/lastEdited/revisionNumber） | 指定値のいずれか |
| ascending | boolean | No | 昇順ソートかどうか | - |
| offset | number | No | 検索結果のオフセット | 0以上の整数 |
| limit | number | No | 検索結果の最大件数（最大12） | 1-12の整数 |
| padId | string | Yes | 削除・クリーンアップ対象のパッドID | 有効なパッドID |
| padName | string | Yes | 作成するパッド名 | 有効なパッド名 |

### 入力データソース

- 管理画面からのSocket.IOメッセージ
- 管理者セッション情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| total | number | 検索条件に一致するパッド総数 |
| results | PadQueryResult[] | パッド情報の配列 |
| results[].padName | string | パッド名 |
| results[].userCount | number | アクティブユーザー数 |
| results[].lastEdited | number | 最終編集タイムスタンプ |
| results[].revisionNumber | number | 現在のリビジョン番号 |

### 出力先

- Socket.IOによるクライアントへのリアルタイム送信

## 処理フロー

### 処理シーケンス

```
1. クライアントがSocket.IOで/settingsに接続
   └─ 管理者権限の確認（is_admin: true）
2. パッド読み込みリクエスト（padLoad）
   └─ padManager.listAllPads()で全パッドID取得
   └─ patternによるフィルタリング
   └─ ソート処理
   └─ ページネーション適用
   └─ 各パッドの詳細情報取得
3. 結果をクライアントに送信
   └─ results:padLoad イベントで送信
4. パッド削除リクエスト（deletePad）
   └─ パッドの存在確認
   └─ pad.remove()で削除
   └─ results:deletePad で通知
5. パッド作成リクエスト（createPad）
   └─ 既存パッドチェック
   └─ padManager.getPad()で作成
   └─ results:createPad で通知
6. リビジョンクリーンアップ（cleanupPadRevisions）
   └─ cleanup.enabled確認
   └─ deleteRevisions()実行
   └─ results:cleanupPadRevisions で通知
```

### フローチャート

```mermaid
flowchart TD
    A[Socket.IO接続] --> B{管理者権限?}
    B -->|No| C[接続拒否]
    B -->|Yes| D[リクエスト待機]
    D --> E{リクエスト種別}
    E -->|padLoad| F[パッド一覧取得]
    E -->|deletePad| G[パッド削除]
    E -->|createPad| H[パッド作成]
    E -->|cleanupPadRevisions| I[リビジョンクリーンアップ]
    F --> J[フィルタリング]
    J --> K[ソート]
    K --> L[ページネーション]
    L --> M[詳細情報取得]
    M --> N[結果送信]
    G --> O{パッド存在?}
    O -->|Yes| P[pad.remove]
    O -->|No| Q[何もしない]
    P --> N
    H --> R{既存パッド?}
    R -->|Yes| S[エラー返却]
    R -->|No| T[パッド作成]
    T --> N
    I --> U{cleanup有効?}
    U -->|No| V[エラー返却]
    U -->|Yes| W[deleteRevisions]
    W --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-63-01 | 最大取得件数 | 1回のリクエストで最大12件まで取得可能 | padLoad時 |
| BR-63-02 | クリーンアップ設定 | cleanup.enabledがtrueの場合のみクリーンアップ可能 | cleanupPadRevisions時 |
| BR-63-03 | パッド名重複禁止 | 既存パッド名と同じ名前では作成不可 | createPad時 |
| BR-63-04 | 保持リビジョン数 | cleanup.keepRevisionsで指定された数のリビジョンを保持 | cleanupPadRevisions時 |

### 計算ロジック

ソート処理の例（リビジョン番号順）:
```typescript
padMapping.sort((a, b) => {
  if (a.revisionNumber < b.revisionNumber) return query.ascending ? -1 : 1;
  if (a.revisionNumber > b.revisionNumber) return query.ascending ? 1 : -1;
  return 0;
})
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| padLoad | pad:* | SELECT | パッド一覧とメタ情報の取得 |
| deletePad | pad:*, pad:*:revs:*, pad:*:chat:* | DELETE | パッドと関連データの削除 |
| createPad | pad:* | INSERT | 新規パッドの作成 |
| cleanupPadRevisions | pad:*:revs:* | DELETE | 古いリビジョンの削除 |

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

#### pad:{padId}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | atext, pool, head, chatHead | パッドID指定 | パッド本体情報 |
| DELETE | 全項目 | パッドID指定 | パッド削除時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 重複エラー | 既存パッド名で作成しようとした | "Pad already exists"を返却 |
| - | クリーンアップ無効 | cleanup.enabledがfalse | エラーメッセージを返却 |
| - | クリーンアップ失敗 | 内部エラー発生 | エラー詳細を返却 |

### リトライ仕様

エラー時は自動リトライなし。

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

パッド削除時は関連データ（リビジョン、チャット、著者関連）もすべて削除される。削除処理は非同期で並列実行される。

## パフォーマンス要件

- パッド一覧取得: パッド数に依存（全パッドをスキャン）
- 最大12件ずつのページネーションで負荷軽減

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

- 管理者認証必須（is_admin: true）
- パッド削除は復元不可のため注意が必要
- 他のユーザーが編集中のパッドも削除可能

## 備考

- パッド削除時はそのパッドに接続中のユーザーがキックされる
- リビジョンクリーンアップ後は古い履歴にアクセス不可

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | PadSearchQuery.ts | `src/node/types/PadSearchQuery.ts` | PadSearchQuery、PadQueryResult型を確認 |
| 1-2 | PadSearch.ts | `admin/src/utils/PadSearch.ts` | フロントエンド側の型定義 |

**読解のコツ**: 検索クエリと結果の構造を先に理解する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | adminsettings.ts | `src/node/hooks/express/adminsettings.ts` | Socket.IOハンドラの実装 |

**主要処理フロー**:
1. **106-241行目**: padLoadハンドラ - パッド一覧取得とソート
2. **244-252行目**: deletePadハンドラ - パッド削除
3. **258-271行目**: createPadハンドラ - パッド作成
4. **273-305行目**: cleanupPadRevisionsハンドラ - リビジョンクリーンアップ

#### Step 3: パッド管理層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | PadManager.ts | `src/node/db/PadManager.ts` | パッド管理の中核ロジック |
| 3-2 | Pad.ts | `src/node/db/Pad.ts` | パッドクラスの実装 |

**主要処理フロー**:
- **PadManager.ts 146-150行目**: listAllPads関数
- **PadManager.ts 109-144行目**: getPad関数
- **Pad.ts 557-615行目**: remove関数 - パッド削除処理

#### Step 4: クリーンアップ処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | Cleanup.ts | `src/node/utils/Cleanup.ts` | リビジョンクリーンアップ処理 |

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

```
PadPage.tsx (フロントエンド)
    │
    ├─ settingsSocket.emit('padLoad', searchParams)
    │      │
    │      └─ adminsettings.ts: socket.on('padLoad')
    │             └─ padManager.listAllPads()
    │             └─ padManager.getPad() × N
    │             └─ api.padUsersCount()
    │             └─ pad.getLastEdit()
    │
    ├─ settingsSocket.emit('deletePad', padId)
    │      │
    │      └─ adminsettings.ts: socket.on('deletePad')
    │             └─ padManager.getPad()
    │             └─ pad.remove()
    │                    └─ padMessageHandler.kickSessionsFromPad()
    │                    └─ db.remove() × N
    │                    └─ authorManager.removePad() × N
    │
    └─ settingsSocket.emit('cleanupPadRevisions', padId)
           │
           └─ adminsettings.ts: socket.on('cleanupPadRevisions')
                  └─ deleteRevisions(padId, keepRevisions)
```

### データフロー図

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

管理者操作 ───▶ Socket.IO ───▶ adminsettings.ts ───▶ Socket.IO
     │                              │                    │
     │                              ▼                    ▼
     │                      PadManager ───▶ クライアント画面
     │                              │
     │                              ▼
     │                         Pad.ts
     │                              │
     │                              ▼
     │                      ueberDB2 ───▶ データベース
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| adminsettings.ts | `src/node/hooks/express/adminsettings.ts` | ソース | Socket.IOハンドラ |
| PadManager.ts | `src/node/db/PadManager.ts` | ソース | パッド管理 |
| Pad.ts | `src/node/db/Pad.ts` | ソース | パッドクラス |
| PadPage.tsx | `admin/src/pages/PadPage.tsx` | ソース | フロントエンドUI |
| PadSearchQuery.ts | `src/node/types/PadSearchQuery.ts` | ソース | 型定義 |
| Cleanup.ts | `src/node/utils/Cleanup.ts` | ソース | クリーンアップ処理 |
| API.ts | `src/node/db/API.ts` | ソース | padUsersCount関数 |
