# 機能設計書 41-プロキシバックエンド

## 概要

本ドキュメントは、Decap CMSのプロキシバックエンド（`decap-cms-backend-proxy`）の機能設計を記述する。ローカル開発環境でDecap Serverと連携し、HTTPプロキシ経由でGit操作を行うバックエンド実装である。

### 本機能の処理概要

**業務上の目的・背景**：ローカル開発環境においてGitHubやGitLabなどのリモートGitサービスへの直接アクセスなしにCMSのコンテンツ管理を行うことが必要となる場面がある。プロキシバックエンドは、ローカルで動作するDecap Server（プロキシサーバー）を介してGit操作を抽象化し、開発者がオフライン環境や認証不要な環境でもCMSを操作できるようにする。

**機能の利用シーン**：ローカル開発環境でDecap CMSのコンテンツ編集を行う場合に使用される。config.ymlで`local_backend: true`を設定するか、直接`backend.name: proxy`と`backend.proxy_url`を指定することで有効化される。主に開発者がサイトのコンテンツをローカルで編集・プレビューする際に利用される。

**主要な処理内容**：
1. プロキシURL経由でのHTTP POSTリクエストによるDecap Serverとの通信
2. エントリの読み込み（フォルダ/ファイル指定）、取得、保存、削除
3. メディアファイルの一覧取得、個別取得、保存、削除
4. エディトリアルワークフロー対応（未公開エントリの管理、ステータス更新、公開）
5. アセットのBase64シリアライズ/デシリアライズ
6. 認証不要のダミー認証（ローカル開発用）

**関連システム・外部連携**：Decap Server（`decap-server`パッケージ）とHTTP通信で連携する。プロキシURLは通常`http://localhost:8081/api/v1`である。

**権限による制御**：ローカル開発用途のため、認証・認可の制御は行わない。`authenticate()`は即座にPromise.resolve()を返す。

## 関連画面

本機能はバックエンドの実装であるため、特定の画面には直接紐づかない。全ての画面（コレクション一覧、エントリ編集、メディアライブラリ等）のバックエンドとして機能する。

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 全画面共通 | バックエンド | エントリ/メディアのCRUD操作のバックエンド処理 |

## 機能種別

CRUD操作 / データ連携

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| config.backend.proxy_url | string | Yes | プロキシサーバーのURL | 必須。未指定時はError |
| config.backend.branch | string | No | 対象ブランチ名 | デフォルト: 'master' |
| config.media_folder | string | Yes | メディアファイルフォルダパス | 設定ファイルより取得 |
| config.backend.cms_label_prefix | string | No | CMSラベルプレフィックス | - |

### 入力データソース

- CMS設定ファイル（config.yml）: バックエンド設定パラメータ
- ユーザー操作: エントリデータ、メディアファイル

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| entries | Array | エントリデータの配列（file.path, data） |
| mediaFiles | Array | メディアファイルの配列（id, name, path, file, size, url, displayURL） |
| unpublishedEntry | UnpublishedEntry | 未公開エントリ情報（collection, slug, status, diffs, updatedAt） |
| status | object | バックエンドステータス（auth.status, api.status） |

### 出力先

- CMS状態管理（Redux Store）: 読み込んだエントリ/メディアデータ
- プロキシサーバー: 保存/削除リクエスト

## 処理フロー

### 処理シーケンス

```
1. コンストラクタでの初期化
   └─ proxy_urlの必須チェック、branch/mediaFolder/cmsLabelPrefixの設定
2. リクエスト送信（request メソッド）
   └─ POSTリクエストをproxy_urlに送信（Content-Type: application/json）
   └─ レスポンスのJSONパース
   └─ エラー時はAPIError例外をスロー
3. エントリ操作
   └─ entriesByFolder/entriesByFiles/getEntry: 読み込み
   └─ persistEntry: アセットのBase64シリアライズ後に保存
   └─ deleteFiles: 削除
4. メディア操作
   └─ getMedia/getMediaFile: 読み込み＋デシリアライズ
   └─ persistMedia: Base64シリアライズ後に保存
5. ワークフロー操作
   └─ unpublishedEntries/unpublishedEntry: 未公開一覧/単一取得
   └─ updateUnpublishedEntryStatus: ステータス更新
   └─ publishUnpublishedEntry: 公開（メインブランチマージ）
   └─ deleteUnpublishedEntry: 未公開エントリ削除
```

### フローチャート

```mermaid
flowchart TD
    A[CMSクライアント操作] --> B[ProxyBackendメソッド呼び出し]
    B --> C[requestメソッド]
    C --> D[HTTP POST to proxy_url]
    D --> E{レスポンスOK?}
    E -->|Yes| F[JSONレスポンスを返却]
    E -->|No| G[APIErrorをスロー]
    F --> H{メディアファイル?}
    H -->|Yes| I[deserializeMediaFile]
    H -->|No| J[そのまま返却]
    I --> J
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-41-01 | プロキシURL必須 | proxy_urlが未指定の場合はエラーとする | コンストラクタ実行時 |
| BR-41-02 | デフォルトブランチ | branchが未指定の場合は'master'を使用 | コンストラクタ実行時 |
| BR-41-03 | 認証スキップ | 認証は常に成功として扱う | authenticate()呼び出し時 |
| BR-41-04 | 未公開エントリ404 | 未公開エントリが見つからない場合（404）はEditorialWorkflowErrorを投げる | unpublishedEntry()呼び出し時 |
| BR-41-05 | アセットBase64変換 | アセットはBase64エンコードしてサーバーに送信する | persistEntry/persistMedia呼び出し時 |

### 計算ロジック

特筆すべき計算ロジックなし。

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

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

本機能はデータベースを直接操作しない。全てのデータ操作はプロキシサーバーを介したHTTP通信で行われる。

| 操作 | 対象 | 操作種別 | 概要 |
|-----|------|---------|------|
| entriesByFolder | プロキシサーバー | POST(SELECT相当) | フォルダ内エントリ一覧取得 |
| persistEntry | プロキシサーバー | POST(INSERT/UPDATE相当) | エントリ保存 |
| deleteFiles | プロキシサーバー | POST(DELETE相当) | ファイル削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | proxy_urlが未設定 | config.ymlにproxy_urlを設定する |
| HTTPステータスコード | APIError | プロキシサーバーからのエラーレスポンス | サーバーログを確認 |
| 404 | EditorialWorkflowError | 未公開エントリが存在しない | エントリが存在するか確認 |

### リトライ仕様

リトライ機能は本バックエンド自体には実装されていない。`unsentRequest.fetchWithTimeout`によるタイムアウトのみ。

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

HTTP通信ベースのため、トランザクション制御はプロキシサーバー側で行われる。クライアント側では単一リクエスト単位での操作となる。

## パフォーマンス要件

ローカル開発環境での使用を前提としているため、特段のパフォーマンス要件は定義されていない。通信はlocalhost内で完結する。

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

- 認証はバイパスされるため、本番環境での使用は想定していない
- プロキシサーバーはlocalhostおよび127.0.0.1からのアクセスのみを許可（No.45ローカルバックエンド自動検出による制御）
- アセットデータはBase64エンコードされてHTTPボディで送信される

## 備考

- `isGitBackend()`は`false`を返す。直接的なGit操作は行わず、プロキシサーバーがGit操作を担当する
- `status()`は常に`{ auth: { status: true }, api: { status: true } }`を返す
- Decap Server（No.46）と対になる機能である

---

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

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

### 推奨読解順序

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

まず、プロキシバックエンドが扱うデータ型を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | implementation.ts | `packages/decap-cms-backend-proxy/src/implementation.ts` | MediaFile型（26-32行目）およびImplementationインターフェースの理解 |

**読解のコツ**: `Implementation`インターフェースは`decap-cms-lib-util`から提供される。全バックエンドが実装すべき共通インターフェースであり、`entriesByFolder`、`persistEntry`、`getMedia`などのメソッドが定義されている。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/decap-cms-backend-proxy/src/index.ts` | パッケージのエクスポート構造。ProxyBackendクラスとAuthenticationPageをエクスポート |

**主要処理フロー**:
1. **1-2行目**: ProxyBackendとAuthenticationPageをインポート
2. **4-8行目**: 名前付きエクスポートとオブジェクトエクスポートの両方を提供

#### Step 3: ProxyBackendクラスの初期化処理

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | implementation.ts | `packages/decap-cms-backend-proxy/src/implementation.ts` | コンストラクタ（58-68行目）でproxy_url必須チェックとプロパティ初期化 |

**主要処理フロー**:
- **58-68行目**: コンストラクタ。proxy_urlの存在チェック、branch/proxyUrl/mediaFolder/cmsLabelPrefixの初期化
- **70-72行目**: isGitBackend()は常にfalseを返す
- **74-76行目**: status()は常に正常ステータスを返す
- **86-88行目**: authenticate()は即座にPromise.resolve()を返す

#### Step 4: コア通信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | implementation.ts | `packages/decap-cms-backend-proxy/src/implementation.ts` | requestメソッド（98-112行目）が全てのAPI通信の基盤 |

**主要処理フロー**:
- **98-112行目**: `request()`メソッド。action名とparamsをJSONでPOST送信。レスポンスがOKならJSONを返し、エラーならAPIErrorをスロー
- **21-24行目**: `serializeAsset()` - AssetProxyをBase64に変換
- **34-49行目**: `deserializeMediaFile()` - Base64からBlobを生成しURLを作成

#### Step 5: エントリ操作メソッドを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | implementation.ts | `packages/decap-cms-backend-proxy/src/implementation.ts` | エントリCRUDメソッド群（114-221行目） |

**主要処理フロー**:
- **114-119行目**: `entriesByFolder()` - フォルダ内のエントリ取得
- **128-133行目**: `getEntry()` - 単一エントリ取得
- **189-201行目**: `persistEntry()` - アセットをBase64変換後にエントリ保存
- **142-164行目**: `unpublishedEntry()` - 未公開エントリ取得（404時はEditorialWorkflowError）

#### Step 6: 認証ページを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | AuthenticationPage.js | `packages/decap-cms-backend-proxy/src/AuthenticationPage.js` | ログインボタンのみの簡素な認証UI |

**主要処理フロー**:
- **49-52行目**: `handleLogin` - onLoginコールバックを直接呼び出し（認証処理なし）
- **54-66行目**: `render()` - ロゴとログインボタンを表示

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

```
CMSアプリケーション
    |
    +-- ProxyBackend (constructor)
    |       +-- config.backend.proxy_url チェック
    |
    +-- ProxyBackend.request()
    |       +-- unsentRequest.fetchWithTimeout()
    |       +-- JSON.parse()
    |       +-- APIError (エラー時)
    |
    +-- ProxyBackend.entriesByFolder()
    |       +-- request({ action: 'entriesByFolder' })
    |
    +-- ProxyBackend.persistEntry()
    |       +-- serializeAsset() [各アセット]
    |       +-- request({ action: 'persistEntry' })
    |
    +-- ProxyBackend.getMedia()
    |       +-- request({ action: 'getMedia' })
    |       +-- deserializeMediaFile() [各ファイル]
    |
    +-- ProxyBackend.persistMedia()
            +-- serializeAsset()
            +-- request({ action: 'persistMedia' })
            +-- deserializeMediaFile()
```

### データフロー図

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

CMS操作           ──▶  ProxyBackend.request()    ──▶  Decap Server
(エントリ/メディア)        HTTP POST (JSON)              (プロキシサーバー)

Decap Server       ──▶  レスポンスJSON             ──▶  Redux Store
(レスポンス)               deserializeMediaFile()         (CMS状態)

AssetProxy         ──▶  serializeAsset()           ──▶  Base64 JSON
(バイナリデータ)           toBase64()                     (リクエストボディ)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| implementation.ts | `packages/decap-cms-backend-proxy/src/implementation.ts` | ソース | ProxyBackendクラスの実装 |
| index.ts | `packages/decap-cms-backend-proxy/src/index.ts` | ソース | パッケージエントリポイント |
| AuthenticationPage.js | `packages/decap-cms-backend-proxy/src/AuthenticationPage.js` | ソース | 認証ページUI |
| config.ts | `packages/decap-cms-core/src/actions/config.ts` | ソース | ローカルバックエンド検出とプロキシ設定 |
