# 機能設計書 13-読み取り専用ID取得

## 概要

本ドキュメントは、Etherpadにおける読み取り専用ID取得機能（getReadOnlyID）の設計仕様を定義する。この機能は、編集可能なパッドIDから対応する読み取り専用IDを取得するためのAPIを提供する。

### 本機能の処理概要

読み取り専用ID取得機能は、通常のパッドIDに対応する読み取り専用ID（r.で始まるID）を生成・取得する機能である。読み取り専用IDを使用すると、パッドの内容を閲覧のみ可能で編集はできない状態でアクセスできる。

**業務上の目的・背景**：
共同編集ドキュメントを外部に公開する際、編集権限を与えずに閲覧のみを許可したいケースがある。読み取り専用IDを発行することで、ドキュメントの内容を安全に共有できる。これにより、レビュー用の公開、ドキュメントの参照共有、プレゼンテーション時の表示など、様々な閲覧専用シーンに対応できる。

**機能の利用シーン**：
- ドキュメントのレビュー依頼時（編集不可で共有）
- 社内外への資料公開
- 会議資料の画面共有用リンク生成
- 埋め込み表示用のURLとして利用
- バックアップや監査目的での参照リンク生成

**主要な処理内容**：
1. 対象パッドの存在確認
2. 既存の読み取り専用IDの検索
3. 存在しない場合は新規生成（r. + 16文字のランダム文字列）
4. パッドIDと読み取り専用IDの相互参照をデータベースに保存
5. 読み取り専用IDの返却

**関連システム・外部連携**：
- REST API（GET /api/2/pads/readonly）を通じて外部システムから呼び出し可能
- 読み取り専用IDでアクセスした場合、編集機能が無効化される

**権限による制御**：
- APIキーまたはOAuth2トークンによる認証が必要
- 読み取り専用ID自体は認証なしでアクセス可能（パッドの公開設定による）

## 関連画面

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

## 機能種別

データ参照・生成（Read/Create操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| padID | string | Yes | 読み取り専用IDを取得するパッドのID | 正規表現 `^(g.[a-zA-Z0-9]{16}\$)?[^$]{1,50}$` に一致、存在するパッドであること |

### 入力データソース

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

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| code | number | 結果コード（0: 成功、1: エラー、4: 認証エラー） |
| message | string | 結果メッセージ（"ok" または エラーメッセージ） |
| data | object | 読み取り専用IDを含むオブジェクト |
| data.readOnlyID | string | 読み取り専用ID（r.で始まる17文字） |

### 出力先

HTTPレスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ GET /api/2/pads/readonly?padID=xxx
2. 認証チェック
   └─ APIキーまたはOAuth2トークンの検証
3. パラメータ抽出
   └─ padIDの取得とサニタイズ
4. パッド存在確認
   └─ getPadSafe(padID, true)でパッドの存在を確認
5. 読み取り専用ID取得/生成
   └─ readOnlyManager.getReadOnlyId(padID)
6. レスポンス返却
   └─ {code: 0, message: "ok", data: {readOnlyID: "r.xxx"}}
```

### フローチャート

```mermaid
flowchart TD
    A[開始: getReadOnlyID API呼び出し] --> B{認証チェック}
    B -->|失敗| C[401 Unauthorized]
    B -->|成功| D[padIDでパッド存在確認]
    D -->|存在しない| E[400 padID does not exist]
    D -->|存在する| F[pad2readonly:padID を検索]
    F --> G{既存ID存在?}
    G -->|Yes| H[既存IDを返却]
    G -->|No| I[新規ID生成: r. + randomString]
    I --> J[pad2readonly:padID に保存]
    J --> K[readonly2pad:roID に保存]
    K --> L[新規IDを返却]
    H --> M[200 OK: readOnlyID返却]
    L --> M
    M --> N[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-013-01 | ID形式 | 読み取り専用IDは `r.` + 16文字のランダム文字列 | 新規生成時 |
| BR-013-02 | 一意性 | 同一パッドに対しては常に同じ読み取り専用IDを返却 | 常時 |
| BR-013-03 | 双方向マッピング | パッドIDと読み取り専用IDは双方向で参照可能 | 常時 |
| BR-013-04 | パッド存在確認 | 存在しないパッドに対してはエラーを返却 | 常時 |

### 計算ロジック

読み取り専用IDの生成:
```
readOnlyID = "r." + randomString(16)
```
- randomString: a-zA-Z0-9からランダムに16文字を選択

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 検索 | pad2readonly:{padID} | SELECT | 既存の読み取り専用IDを検索 |
| 登録 | pad2readonly:{padID} | INSERT | 新規読み取り専用IDを登録（初回のみ） |
| 登録 | readonly2pad:{readOnlyID} | INSERT | 逆引き用マッピングを登録（初回のみ） |

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

#### pad2readonly:{padID}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | value | キー `pad2readonly:{padID}` | 既存ID検索 |
| INSERT | value | `r.{randomString(16)}` | 新規生成時のみ |

#### readonly2pad:{readOnlyID}

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | value | `{padID}` | 新規生成時のみ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 1 | apierror | パッドが存在しない | 正しいパッドIDを指定 |
| 1 | apierror | パッドIDの形式が不正 | 正しい形式のIDを指定 |
| 4 | authError | 認証情報が無効 | 正しいAPIキーまたはトークンを使用 |

### リトライ仕様

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

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

読み取り専用ID生成時、pad2readonlyとreadonly2padへの書き込みはPromise.allで並列実行される。どちらかが失敗した場合、部分的に書き込まれる可能性があるが、次回呼び出し時に正しく再生成される。

## パフォーマンス要件

- キーによる直接参照のため、高速なレスポンスが期待できる
- 新規生成時もランダム文字列生成とDB書き込みのみで軽量

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

- APIキーまたはOAuth2認証が必須（読み取り専用ID取得時）
- 読み取り専用IDは推測困難なランダム文字列
- 読み取り専用IDでのアクセスはパッドの公開設定に依存

## 備考

- 読み取り専用IDは `r.` プレフィックスで識別可能
- パッド削除時には関連する読み取り専用マッピングも削除される
- 同一パッドに対する複数回の呼び出しは同じIDを返却（冪等性）

---

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

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

### 推奨読解順序

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

読み取り専用IDの管理構造を把握することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ReadOnlyManager.ts | `src/node/db/ReadOnlyManager.ts` | 読み取り専用ID管理モジュール全体 |

**読解のコツ**: DBに保存される2つのキー（pad2readonly:, readonly2pad:）の関係を理解する。双方向マッピングにより、どちらからでも逆引き可能。

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

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

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

**主要処理フロー**:
1. **681-695行目**: GET /pads/readonly のルート定義
2. **57行目**: getReadOnlyIDの引数（padID）

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

実際のID取得/生成ロジックを読み解く。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | API.ts | `src/node/db/API.ts` | getReadOnlyID関数（665-673行目） |
| 3-2 | ReadOnlyManager.ts | `src/node/db/ReadOnlyManager.ts` | getReadOnlyId関数（39-53行目） |

**主要処理フロー**:
- **API.ts 665-673行目**: getPadSafeでパッド確認後、readOnlyManager.getReadOnlyIdを呼び出し
- **ReadOnlyManager.ts 41行目**: pad2readonly:からの検索
- **ReadOnlyManager.ts 44-49行目**: 存在しない場合の新規生成・保存

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

```
GET /api/2/pads/readonly (RestAPI.ts)
    │
    └─ apiHandler.handle() (APIHandler.ts)
           │
           └─ api.getReadOnlyID() (API.ts:665-673)
                  │
                  ├─ getPadSafe(padID, true) (API.ts:667)
                  │      └─ パッド存在確認
                  │
                  └─ readOnlyManager.getReadOnlyId(padID) (ReadOnlyManager.ts:39-53)
                         │
                         ├─ db.get(`pad2readonly:${padID}`) (41行目)
                         │
                         └─ [既存IDなし]
                                ├─ randomString(16) (45行目)
                                └─ Promise.all [
                                       db.set(`pad2readonly:${padID}`, readOnlyId),
                                       db.set(`readonly2pad:${readOnlyId}`, padID)
                                   ] (46-49行目)
```

### データフロー図

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

padID         ───────▶ getPadSafe() ──────────────▶ パッド存在確認
                              │
                              ▼
                   readOnlyManager.getReadOnlyId()
                              │
                              ├─▶ db.get(pad2readonly:)
                              │         │
                              │         ├─[存在] ─▶ 既存ID返却
                              │         │
                              │         └─[不在] ─▶ 新規生成
                              │                         │
                              │                         ├─▶ r. + randomString(16)
                              │                         │
                              │                         ├─▶ pad2readonly:{padID} [SET]
                              │                         │
                              │                         └─▶ readonly2pad:{roID} [SET]
                              │
                              ▼
                       {readOnlyID: "r.xxx"} ───────▶ JSON Response
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| API.ts | `src/node/db/API.ts` | ソース | getReadOnlyID関数の実装 |
| ReadOnlyManager.ts | `src/node/db/ReadOnlyManager.ts` | ソース | 読み取り専用ID管理の中核ロジック |
| RestAPI.ts | `src/node/handler/RestAPI.ts` | ソース | REST APIエンドポイント定義 |
| APIHandler.ts | `src/node/handler/APIHandler.ts` | ソース | API呼び出しハンドリング |
| randomstring.ts | `src/node/utils/randomstring.ts` | ソース | ランダム文字列生成 |
| DB.ts | `src/node/db/DB.ts` | ソース | データベースアクセス層 |
