# 機能設計書 33-カスタムリダイレクト

## 概要

本ドキュメントは、GhostのカスタムリダイレクトURL設定機能に関する設計仕様を定義する。この機能により、サイト管理者はURLのリダイレクトルールをJSON/YAML形式で定義し、旧URLから新URLへのリダイレクトを設定できる。

### 本機能の処理概要

カスタムリダイレクトは、`redirects.json`または`redirects.yaml`ファイルを使用してURLリダイレクトルールを管理する機能である。正規表現によるパターンマッチングをサポートし、301（永続的）または302（一時的）リダイレクトを設定できる。

**業務上の目的・背景**：サイトのURL構造を変更した際に、古いURLへのアクセスを新しいURLに転送することで、SEOの評価を維持し、ユーザーエクスペリエンスを向上させる。また、外部からのリンク切れを防ぎ、サイトの信頼性を維持する。

**機能の利用シーン**：
- サイトリニューアルでURL構造を変更した場合
- 記事のスラッグ（URL）を変更した場合
- 複数の類似URLを1つに統合したい場合
- 一時的なキャンペーンページへのリダイレクトを設定したい場合
- ドメイン移行時の旧URLの転送

**主要な処理内容**：
1. `redirects.json`または`redirects.yaml`ファイルの読み込みと解析
2. リダイレクトルールのバリデーション（形式チェック、正規表現の有効性）
3. リダイレクトマネージャーへのルール登録
4. HTTPリクエスト時のリダイレクト処理（ミドルウェア）
5. Admin APIを通じたリダイレクトファイルのアップロード・取得

**関連システム・外部連携**：
- Express.jsミドルウェア：リダイレクト処理の実行
- Admin API：リダイレクトファイルの管理

**権限による制御**：管理者（Administrator）以上のロールを持つスタッフユーザーのみがリダイレクト設定をアップロード・変更できる。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | Labs設定 | 参照画面 | リダイレクトファイルのアップロード（Settings > Labs） |

## 機能種別

設定管理 / URL転送処理

## 入力仕様

### 入力パラメータ

#### JSON形式

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| from | String | Yes | 転送元URLパターン（正規表現対応） | 有効な正規表現であること |
| to | String | Yes | 転送先URL（動的値`$1`等使用可） | 文字列であること |
| permanent | Boolean | No | true=301、false=302（デフォルト: false） | true/falseのみ |

#### YAML形式

```yaml
301:
  /old-url/: /new-url/
302:
  /temp-redirect/: /destination/
```

### 入力データソース

- `content/data/redirects.json`または`content/data/redirects.yaml`ファイル
- Admin API経由でのファイルアップロード

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| redirects | Array | リダイレクトルールの配列（JSON形式取得時） |
| content | String | リダイレクトファイルの内容（YAML形式取得時） |

### 出力先

- HTTPレスポンス（301/302リダイレクト）
- Admin APIレスポンス

## 処理フロー

### 処理シーケンス

```
1. 初期化処理
   └─ redirects.json/yaml ファイルの存在確認
   └─ ファイルの読み込みと解析
   └─ バリデーション実行
   └─ リダイレクトマネージャーへのルール登録

2. リダイレクト処理（ミドルウェア）
   └─ HTTPリクエストのパスを取得
   └─ 登録されたルールとパターンマッチング
   └─ マッチした場合、301/302リダイレクトを返却

3. ファイルアップロード（Admin API）
   └─ アップロードファイルの解析
   └─ バリデーション実行
   └─ 既存ファイルのバックアップ作成
   └─ 新ファイルの保存
   └─ リダイレクトマネージャーの更新
```

### フローチャート

```mermaid
flowchart TD
    A[Ghost起動] --> B{redirectsファイル存在?}
    B -->|Yes| C[ファイル読み込み]
    B -->|No| D[初期化完了]
    C --> E[JSON/YAML解析]
    E --> F{バリデーション}
    F -->|成功| G[ルール登録]
    F -->|失敗| H[エラーログ出力]
    G --> D
    H --> D

    I[HTTPリクエスト] --> J[リダイレクトミドルウェア]
    J --> K{パターンマッチ?}
    K -->|Yes| L{永続的?}
    K -->|No| M[次のミドルウェアへ]
    L -->|Yes| N[301リダイレクト]
    L -->|No| O[302リダイレクト]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-33-01 | ファイル形式 | redirects.yamlが優先、存在しなければredirects.json | ファイル検索時 |
| BR-33-02 | 正規表現サポート | fromパラメータは正規表現として解釈される | パターンマッチング時 |
| BR-33-03 | 動的置換 | toパラメータで$1, $2等のキャプチャグループ参照が可能 | リダイレクト実行時 |
| BR-33-04 | デフォルトは302 | permanentが指定されない場合は一時的リダイレクト | リダイレクト実行時 |
| BR-33-05 | バックアップ作成 | ファイル更新時は既存ファイルのバックアップを作成 | Admin APIでの更新時 |
| BR-33-06 | サブディレクトリ対応 | GhostがサブディレクトリでホストされていてもURLを正しく処理 | パターンマッチング時 |

### 計算ロジック

- リダイレクトURLの構築: `urlUtils.urlJoin(urlUtils.getSubdir(), to)`
- 正規表現マッチング: `new RegExp(from).test(requestPath)`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | データベースは使用しない（ファイルベース） |

### ファイル操作詳細

| 操作 | ファイル | 処理内容 | 備考 |
|-----|---------|---------|------|
| 読み込み | content/data/redirects.yaml | YAML形式のリダイレクト設定読み込み | 優先 |
| 読み込み | content/data/redirects.json | JSON形式のリダイレクト設定読み込み | YAMLが存在しない場合 |
| 書き込み | content/data/redirects.json/yaml | アップロードされた設定の保存 | Admin API経由 |
| バックアップ | content/data/redirects.json/yaml.bak | 更新前のファイルをバックアップ | 上書き前に作成 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | BadRequestError | JSONパースエラー | 「Could not parse JSON: {context}」エラーを返却 |
| - | BadRequestError | YAMLパースエラー | 「Could not parse YAML: {context}」エラーを返却 |
| - | ValidationError | 不正なリダイレクト形式 | 「Incorrect redirects file format」エラーを返却 |
| - | ValidationError | 不正な正規表現 | 「Incorrect RegEx in redirects file」エラーを返却 |
| - | NotFoundError | ファイルが存在しない | 空の配列を返却（正常終了） |

### リトライ仕様

リトライは不要。バリデーションエラーの場合はファイルを修正して再アップロード。

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

- ファイル更新は既存ファイルのバックアップ作成後に実行
- 更新失敗時はバックアップから復元可能

## パフォーマンス要件

- リダイレクトルールはメモリ上のマネージャーに保持
- 各HTTPリクエストで正規表現マッチングを実行
- キャッシュのmaxAge設定: `config.caching.customRedirects.maxAge`

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

- 管理者権限を持つユーザーのみがリダイレクト設定を変更可能
- オープンリダイレクト脆弱性の注意：外部URLへのリダイレクトを設定する際は注意が必要
- 正規表現DoS（ReDoS）攻撃への注意：複雑な正規表現パターンは避ける

## 備考

- リダイレクトファイルのサンプル形式はGhostドキュメントを参照
- 大量のリダイレクトルールはパフォーマンスに影響する可能性あり
- 起動時にファイル読み込みエラーが発生してもGhostは起動する（エラーログ出力のみ）

---

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

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

### 推奨読解順序

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

まず、リダイレクト設定のデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | custom-redirects-api.js | `ghost/core/core/server/services/custom-redirects/custom-redirects-api.js` | **17-23行目**: RedirectConfig型定義（from, to, permanent） |

**読解のコツ**:
- JSDocコメントで型定義が記載されている
- from: 正規表現パターン、to: 転送先URL、permanent: 301/302の切り替え

#### Step 2: サービス初期化を理解する

カスタムリダイレクトサービスの初期化処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.js | `ghost/core/core/server/services/custom-redirects/index.js` | **12-38行目**: 初期化処理、DynamicRedirectManager生成、CustomRedirectsAPI生成 |

**主要処理フロー**:
1. **13-19行目**: DynamicRedirectManagerの生成（permanentMaxAge, getSubdirectoryURL設定）
2. **21-26行目**: CustomRedirectsAPIの生成（basePath, validate設定）
3. **28行目**: customRedirectsAPI.init()呼び出し

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

リダイレクト設定の読み書き処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | custom-redirects-api.js | `ghost/core/core/server/services/custom-redirects/custom-redirects-api.js` | **120-261行目**: CustomRedirectsAPIクラス |

**主要処理フロー**:
1. **142-172行目**: `init()` - 起動時のファイル読み込みとルール登録
2. **187-204行目**: `getRedirectsFilePath()` - YAML優先でファイルパス取得
3. **212-239行目**: `setFromFilePath()` - ファイルアップロード処理、バックアップ作成
4. **244-257行目**: `get()` - 現在のリダイレクト設定取得

#### Step 4: バリデーションを理解する

リダイレクト設定のバリデーション処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | validation.js | `ghost/core/core/server/services/custom-redirects/validation.js` | **24-54行目**: validate関数 |

**主要処理フロー**:
1. **25-30行目**: 配列形式チェック
2. **32-39行目**: from/toの必須チェック
3. **41-50行目**: fromの正規表現有効性チェック

#### Step 5: ファイル解析を理解する

JSON/YAMLファイルの解析処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | custom-redirects-api.js | `ghost/core/core/server/services/custom-redirects/custom-redirects-api.js` | **54-114行目**: parseRedirectsFile関数 |

**主要処理フロー**:
1. **55-67行目**: JSON形式の解析
2. **69-111行目**: YAML形式の解析（302と301セクションを処理）

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

```
Ghost起動
    │
    └─ custom-redirects/index.js init()
           │
           ├─ DynamicRedirectManager生成
           │
           └─ CustomRedirectsAPI.init()
                  ├─ getRedirectsFilePath()
                  │      └─ YAML/JSONファイル検索
                  │
                  ├─ readRedirectsFile()
                  │      └─ fs.readFile()
                  │
                  ├─ parseRedirectsFile()
                  │      └─ JSON.parse() / yaml.load()
                  │
                  ├─ validation.validate()
                  │      └─ 形式・正規表現チェック
                  │
                  └─ redirectManager.addRedirect()
                         └─ ルール登録

HTTPリクエスト
    │
    └─ redirectManager.handleRequest (ミドルウェア)
           └─ パターンマッチング → 301/302レスポンス
```

### データフロー図

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

redirects.yaml/json ───▶ parseRedirectsFile()
                              │
                              ▼
                        バリデーション
                              │
                              ▼
                        redirectManager.addRedirect()
                              │
                              ▼
                        メモリ上のルール保持
                              │
                              │
HTTPリクエスト ──────────▶ handleRequest
                              │
                              ▼
                        パターンマッチング
                              │
                        ┌─────┴─────┐
                        ▼           ▼
                  マッチあり     マッチなし
                        │           │
                        ▼           ▼
              301/302レスポンス  次へパススルー
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.js | `ghost/core/core/server/services/custom-redirects/index.js` | ソース | サービスエントリーポイント |
| custom-redirects-api.js | `ghost/core/core/server/services/custom-redirects/custom-redirects-api.js` | ソース | API実装、ファイル読み書き |
| validation.js | `ghost/core/core/server/services/custom-redirects/validation.js` | ソース | バリデーション処理 |
| utils.js | `ghost/core/core/server/services/custom-redirects/utils.js` | ソース | ユーティリティ関数 |
| dynamic-redirect-manager.js | `ghost/core/core/server/services/lib/dynamic-redirect-manager.js` | ソース | リダイレクトマネージャー |
