# 機能設計書 19-エントリ公開

## 概要

本ドキュメントは、Decap CMSにおける未公開エントリの公開機能の設計を記述する。エディトリアルワークフロー下で未公開エントリをメインブランチにマージして公開する処理の仕様を網羅する。

### 本機能の処理概要

エントリ公開機能は、エディトリアルワークフローで管理されている未公開エントリ（ブランチ/PR）をメインブランチにマージし、コンテンツを公開状態にする機能である。

**業務上の目的・背景**：エディトリアルワークフローのレビュープロセスを経たエントリを、最終的に公開するための機能である。レビュー承認後のエントリを安全にメインブランチに統合し、サイトのコンテンツとして公開する。

**機能の利用シーン**：エントリ編集画面の「公開」ボタンまたはワークフロー画面から公開操作を行う。通常はPending Publishステータスのエントリが公開対象だが、APIレベルでは任意のステータスから公開可能。

**主要な処理内容**：
1. Redux状態から未公開エントリを取得（`selectUnpublishedEntry`）
2. `UNPUBLISHED_ENTRY_PUBLISH_REQUEST`をディスパッチ
3. `backend.publishUnpublishedEntry(entry)`でバックエンドAPIを呼び出し（ブランチマージ/PR承認）
4. メディアライブラリの再読み込み（loadMedia）
5. 成功通知の表示
6. `UNPUBLISHED_ENTRY_PUBLISH_SUCCESS`をディスパッチ（editorialWorkflow.entitiesから削除）
7. ネストコレクションの場合はエントリ一覧を再読み込みし、スラッグ変更対応を行う
8. 通常コレクションの場合はloadEntryで公開済みエントリを読み込む

**関連システム・外部連携**：Gitバックエンド（GitHub/GitLab/Bitbucket等）のブランチマージ/PR承認APIと連携。

**権限による制御**：公開操作にはメインブランチへの書き込み権限（マージ権限）が必要。Open Authoringモードではリポジトリ管理者のみが公開可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 6 | エントリ編集画面 | 主画面 | Editorial Workflow有効時の公開処理。publishUnpublishedEntryアクションによるメインブランチへのマージ |
| 8 | ワークフロー画面 | 主画面 | ワークフローボードからの直接公開処理 |

## 機能種別

データ公開 - エントリのメインブランチへのマージ（公開）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| collectionName | string | Yes | コレクション名 | - |
| slug | string | Yes | エントリスラッグ | - |

### 入力データソース

エントリ編集画面の「公開」ボタン、またはワークフロー画面のエントリカード操作。

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| - | void | 公開処理の結果は状態変更として反映 |

### 出力先

- バックエンドAPI（Git操作: ブランチマージ/PR承認）
- Redux Store（`editorialWorkflow.entities`からエントリ削除、`entries`に公開済みエントリとして追加）
- UIトースト通知（公開成功/失敗メッセージ）

## 処理フロー

### 処理シーケンス

```
1. ユーザーが「公開」ボタンを押下
   └─ publishUnpublishedEntryアクションをディスパッチ
2. selectUnpublishedEntryでRedux状態から未公開エントリを取得
3. UNPUBLISHED_ENTRY_PUBLISH_REQUESTをディスパッチ
   └─ isPublishing=trueに設定
4. backend.publishUnpublishedEntry(entry)を実行
   └─ バックエンドがブランチマージ/PR承認を実行
5. メディアライブラリの再読み込み（loadMedia）
6. ui.toast.entryPublished通知を表示
7. UNPUBLISHED_ENTRY_PUBLISH_SUCCESSをディスパッチ
   └─ editorialWorkflow.entitiesからエントリを削除
8. コレクションの種類に応じた後処理
   └─ ネストコレクション:
       ├─ loadEntries(collection)で一覧再読み込み
       ├─ slugFromCustomPathで新スラッグ取得
       └─ 編集中の場合: navigateToEntry
   └─ 通常コレクション:
       └─ loadEntry(collection, slug)で公開済みエントリ読み込み
```

### フローチャート

```mermaid
flowchart TD
    A[公開ボタン押下] --> B[selectUnpublishedEntry]
    B --> C[PUBLISH_REQUEST dispatch]
    C --> D[backend.publishUnpublishedEntry]
    D --> E{成功?}
    E -->|Yes| F[loadMedia]
    F --> G[entryPublished通知]
    G --> H[PUBLISH_SUCCESS dispatch]
    H --> I{ネストコレクション?}
    I -->|Yes| J[loadEntries]
    J --> K[slugFromCustomPath]
    K --> L{編集中 and スラッグ変更?}
    L -->|Yes| M[navigateToEntry]
    L -->|No| N[完了]
    I -->|No| O[loadEntry]
    O --> N
    M --> N
    E -->|No| P[PUBLISH_FAILURE dispatch]
    P --> Q[onFailToPublishEntryエラー通知]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-19-01 | エントリ削除 | 公開成功後、editorialWorkflow.entitiesから該当エントリを削除する | PUBLISH_SUCCESS時 |
| BR-19-02 | メディア再読み込み | 公開後にメディアライブラリを再読み込みする | 公開成功後 |
| BR-19-03 | ネストコレクション対応 | ネストコレクションの場合はエントリ一覧を再読み込みし、カスタムパスからスラッグを再計算 | collection.has('nested')時 |
| BR-19-04 | 編集中リダイレクト | 公開後にスラッグが変更された場合、編集中であれば新しいURLに遷移する | 編集中かつスラッグ変更時 |
| BR-19-05 | 通常コレクション | ネストコレクション以外の場合はloadEntryで公開済みエントリを読み込む | 通常コレクション時 |

### 計算ロジック

- 新スラッグ取得（ネストコレクション）: `slugFromCustomPath(collection, entry.get('path'))`
- 編集中判定: `selectEditingDraft(state.entryDraft)`

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

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

| 操作 | 対象 | 操作種別 | 概要 |
|-----|------|---------|------|
| publishUnpublishedEntry | Git API | UPDATE/DELETE | ブランチのメインブランチへのマージ、PRの承認・クローズ |
| loadMedia | Git API / メディアストレージ | SELECT | メディア一覧の再取得 |
| loadEntry/loadEntries | Git API | SELECT | 公開済みエントリの読み込み |

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

Gitバックエンドにより実装が異なる。GitHub APIではPRのマージ、GitLab APIではMerge Requestのマージ操作が行われる。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| UNPUBLISHED_ENTRY_PUBLISH_FAILURE | 公開エラー | バックエンドAPIの失敗（マージコンフリクト等） | エラー通知、isPublishing削除 |

### リトライ仕様

自動リトライは行わない。マージコンフリクトの場合はユーザーが手動で解決する必要がある。

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

Reduxの状態更新はバックエンドの応答後に行われる。isPublishingフラグで公開処理中を管理し、成功時にentitiesからエントリを削除する。失敗時は状態をリセットする。

## パフォーマンス要件

- バックエンドAPI（ブランチマージ）の応答時間に依存
- マージ後のloadMedia/loadEntryが追加的なAPI呼び出しを発生させる
- isPublishingフラグによりUIのボタン重複押下を防止

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

- 公開操作にはメインブランチへのマージ権限が必要
- Open Authoringモードでは、フォーク元リポジトリの管理者権限が必要
- 公開はバックエンドAPI経由で認証される

## 備考

- 公開後はeditorialWorkflow.entitiesからエントリが削除されるため、ワークフロー画面のカンバンボードからも消える
- ネストコレクションでは`slugFromCustomPath`でカスタムパスからスラッグを再計算する必要がある

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | editorialWorkflow.ts (reducer) | `packages/decap-cms-core/src/reducers/editorialWorkflow.ts` | PUBLISH_REQUEST/SUCCESS/FAILUREケース（126-141行目）での状態変更を確認 |

**読解のコツ**: PUBLISH_SUCCESSは`deleteIn`でentitiesからエントリを完全に削除する。これにより、ワークフロー管理下からエントリが外れる。

#### Step 2: アクションクリエータを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | editorialWorkflow.ts (action) | `packages/decap-cms-core/src/actions/editorialWorkflow.ts` | publishUnpublishedEntry関数（482-523行目） |

**主要処理フロー**:
1. **484-487行目**: state/collections/backend/entryの取得
2. **488行目**: PUBLISH_REQUESTをディスパッチ
3. **490行目**: `backend.publishUnpublishedEntry(entry)` を実行
4. **492行目**: `loadMedia()` でメディア再読み込み
5. **500行目**: `PUBLISH_SUCCESS` をディスパッチ
6. **501-508行目**: ネストコレクションの場合の後処理（loadEntries、slugFromCustomPath、navigateToEntry）
7. **510行目**: 通常コレクションの場合は`loadEntry(collection, slug)`

#### Step 3: リデューサーの状態削除を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | editorialWorkflow.ts (reducer) | `packages/decap-cms-core/src/reducers/editorialWorkflow.ts` | PUBLISH_SUCCESSケース（132-133行目）でのdeleteIn |
| 3-2 | entryDraft.js | `packages/decap-cms-core/src/reducers/entryDraft.js` | isPublishing管理（153-158行目） |

**主要処理フロー**:
- **126-130行目**: PUBLISH_REQUESTでisPublishing=true
- **132-133行目**: PUBLISH_SUCCESSでentitiesからエントリを完全削除
- **153-155行目**: entryDraftのPUBLISH_REQUESTでisPublishing=true
- **156-158行目**: PUBLISH_SUCCESS/FAILUREでisPublishingを削除

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

```
エントリ編集画面 / ワークフロー画面（公開ボタン）
    │
    └─ publishUnpublishedEntry(collectionName, slug)
           [actions/editorialWorkflow.ts:482-523]
           │
           ├─ selectUnpublishedEntry(state, collectionName, slug)
           │
           ├─ dispatch(UNPUBLISHED_ENTRY_PUBLISH_REQUEST)
           │
           ├─ backend.publishUnpublishedEntry(entry)
           │      └─ Git API: ブランチマージ / PR承認
           │
           ├─ dispatch(loadMedia())
           │
           ├─ dispatch(UNPUBLISHED_ENTRY_PUBLISH_SUCCESS)
           │      └─ editorialWorkflow reducer: entities削除
           │
           └─ 後処理:
                  ├─ ネスト: loadEntries + slugFromCustomPath + navigateToEntry
                  └─ 通常: loadEntry(collection, slug)
```

### データフロー図

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

collectionName, slug      ───▶  publishUnpublishedEntry      ───▶  Git: ブランチマージ
                                 │
                                 ├─ backend.publishUnpublishedEntry
                                 │
                                 ├─ PUBLISH_SUCCESS dispatch  ───▶  editorialWorkflow.entities削除
                                 │
                                 ├─ loadMedia()              ───▶  メディア一覧更新
                                 │
                                 └─ loadEntry / loadEntries  ───▶  entries.entities更新
                                                                    (公開済みとして読み込み)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| editorialWorkflow.ts (action) | `packages/decap-cms-core/src/actions/editorialWorkflow.ts` | ソース | publishUnpublishedEntryアクション |
| editorialWorkflow.ts (reducer) | `packages/decap-cms-core/src/reducers/editorialWorkflow.ts` | ソース | PUBLISH関連の状態管理 |
| entryDraft.js | `packages/decap-cms-core/src/reducers/entryDraft.js` | ソース | isPublishing管理 |
| entries.ts (action) | `packages/decap-cms-core/src/actions/entries.ts` | ソース | loadEntry、loadEntries |
| backend.ts | `packages/decap-cms-core/src/backend.ts` | ソース | slugFromCustomPath関数 |
| mediaLibrary.ts | `packages/decap-cms-core/src/actions/mediaLibrary.ts` | ソース | loadMedia関数 |
