# 機能設計書 11-ボード公開設定

## 概要

本ドキュメントは、ボードを外部に公開または非公開に設定する機能の設計を記載する。

### 本機能の処理概要

本機能は、カンバンボードをインターネット上で一般公開するか、非公開にするかを制御する機能である。公開されたボードは、認証なしでURLを知っている誰でも閲覧可能となる。

**業務上の目的・背景**：チームで管理しているタスクやプロジェクト状況を、外部のステークホルダーや一般ユーザーに公開したい場面がある。例えば、オープンソースプロジェクトのロードマップ公開、顧客への進捗共有、公開提案ボードの運用などが考えられる。本機能により、ボード管理者は簡単な操作でボードの公開状態を切り替えることができる。

**機能の利用シーン**：ボード設定画面において、管理者権限を持つユーザーがボードの公開/非公開を切り替える。公開時には一意のURLキーが自動生成され、そのURLを共有することで外部からのアクセスが可能となる。

**主要な処理内容**：
1. ボード公開（create）: Board::Publicationレコードを作成し、セキュアなランダムキーを生成
2. ボード非公開化（destroy）: Board::Publicationレコードを削除し、公開URLを無効化
3. 公開状態の確認: publicationの存在有無で判定

**関連システム・外部連携**：公開ボードはPublic::BoardsControllerを通じて外部からアクセスされる。Turbo Streamsを使用してUI更新を行う。

**権限による制御**：ボード管理者権限（can_administer_board?）を持つユーザーのみが公開設定を変更可能。それ以外のユーザーはHTTP 403 Forbiddenが返される。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | ボード設定画面 | 主画面 | ボードを外部に公開/非公開に設定する操作 |
| 67 | 公開ボード詳細画面 | 結果表示画面 | 公開後のボードを外部ユーザーが閲覧する画面 |

## 機能種別

CRUD操作（Create/Delete） / 状態変更処理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| board_id | UUID | Yes | 対象ボードのID | 存在するボードであること、ユーザーがアクセス可能であること |

### 入力データソース

- URLパラメータ: board_idはルーティングから取得
- セッション: 認証済みユーザー情報（Current.user）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| publication.key | String | 公開用の一意キー（24文字のBase58文字列） |
| publication.board_id | UUID | 対象ボードのID |
| publication.created_at | DateTime | 公開日時 |

### 出力先

- データベース: board_publicationsテーブル
- 画面表示: Turbo Streamsによる公開設定UIの更新
- フラッシュメッセージ: "Saved"

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信（POST /boards/:board_id/publication または DELETE /boards/:board_id/publication）
   └─ BoardScopedによりboard_idからボードを取得

2. 権限チェック（ensure_permission_to_admin_board）
   └─ Current.user.can_administer_board?(@board)がfalseの場合は403を返す

3. 公開処理（create）または非公開処理（destroy）
   └─ create: @board.publishを呼び出し → Board::Publicationを作成
   └─ destroy: @board.unpublishを呼び出し → Board::Publicationを削除後reload

4. Turbo Streamsレスポンス
   └─ 公開設定UIパーシャルを更新
   └─ フラッシュメッセージ表示
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{アクション種別}
    B -->|POST create| C[権限チェック]
    B -->|DELETE destroy| C
    C -->|権限なし| D[403 Forbidden]
    C -->|権限あり| E{アクション}
    E -->|create| F[publish呼び出し]
    E -->|destroy| G[unpublish呼び出し]
    F --> H{既に公開済み?}
    H -->|Yes| I[何もしない]
    H -->|No| J[Publication作成]
    G --> K[Publication削除]
    I --> L[Turbo Streams応答]
    J --> L
    K --> M[Board reload]
    M --> L
    L --> N[終了]
    D --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 公開キーの一意性 | 公開キーは全アカウントを通じて一意である | 新規公開時 |
| BR-02 | 公開キーの自動生成 | 公開キーはhas_secure_tokenにより自動生成される | 新規公開時 |
| BR-03 | 再公開時のキー変更 | 一度非公開にして再公開すると新しいキーが生成される | 非公開後の再公開時 |
| BR-04 | 管理者権限の必要性 | ボード管理者のみが公開設定を変更可能 | 常時 |

### 計算ロジック

該当なし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 公開 | board_publications | INSERT | 公開レコードを作成しキーを自動生成 |
| 非公開 | board_publications | DELETE | 公開レコードを削除 |

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

#### board_publications

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | UUID自動生成 | 主キー |
| INSERT | account_id | board.account_id | デフォルト値として設定 |
| INSERT | board_id | 対象ボードのID | 外部キー |
| INSERT | key | SecureRandom.base58(24) | has_secure_tokenによる自動生成 |
| INSERT | created_at | 現在時刻 | 自動設定 |
| INSERT | updated_at | 現在時刻 | 自動設定 |
| DELETE | - | board_id = @board.id | publication&.destroyで削除 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 403 | Forbidden | 管理者権限がないユーザーがアクセス | ensure_permission_to_admin_boardで拒否 |
| 404 | Not Found | 存在しないboard_idが指定された | set_boardでRecordNotFound発生 |
| 422 | Unprocessable Entity | keyのユニーク制約違反（極めて稀） | リトライまたはエラー表示 |

### リトライ仕様

has_secure_tokenによるキー生成は衝突確率が極めて低いため、通常リトライは不要。

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

- 公開（create）: create_publication!は単一のINSERT文でトランザクション不要
- 非公開（destroy）: publication&.destroyは単一のDELETE文でトランザクション不要

## パフォーマンス要件

- レスポンス時間: 200ms以内（単一レコードの作成/削除のため高速）
- board_publicationsテーブルにはboard_idとkeyにインデックスがあり、検索は高速

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

- 認証: ログイン済みユーザーのみアクセス可能
- 認可: ensure_permission_to_admin_boardによりボード管理者権限をチェック
- 公開キー: SecureRandom.base58(24)により推測困難な24文字のキーを生成
- 公開ボードへのアクセス: キーを知っている人のみアクセス可能（URLは推測困難）

## 備考

- 公開されたボードはPublic::BoardsControllerで処理される
- 公開状態の確認は board.published? または board.publicly_accessible? で可能

---

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

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

### 推奨読解順序

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

ボードの公開状態を管理するデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | publication.rb | `app/models/board/publication.rb` | 公開情報を保持するモデル。has_secure_token :keyにより公開キーが自動生成される |
| 1-2 | publishable.rb | `app/models/board/publishable.rb` | Boardモデルに公開機能を追加するconcern。publish/unpublishメソッドの実装 |

**読解のコツ**: has_secure_tokenはRailsの機能で、指定したカラムにSecureRandom.base58(24)で生成された一意のトークンを自動設定する。

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

リクエストを受け付けるコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | publications_controller.rb | `app/controllers/boards/publications_controller.rb` | createで公開、destroyで非公開を実行 |
| 2-2 | board_scoped.rb | `app/controllers/concerns/board_scoped.rb` | BoardScopedでボードの取得と権限チェックを行う |

**主要処理フロー**:
1. **4行目**: before_action :ensure_permission_to_admin_boardで管理者権限チェック
2. **6-8行目**: createアクションで@board.publishを呼び出し
3. **10-13行目**: destroyアクションで@board.unpublishを呼び出し、その後reload

#### Step 3: 公開/非公開ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | publishable.rb | `app/models/board/publishable.rb` | publishメソッド（20-22行目）でcreate_publication!を呼び出す |

**主要処理フロー**:
- **15-17行目**: published?メソッドでpublicationの存在を確認
- **20-22行目**: publishメソッドでPublicationを作成（既に公開済みなら何もしない）
- **24-26行目**: unpublishメソッドでPublicationを削除

#### Step 4: ビューの更新を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create.turbo_stream.erb | `app/views/boards/publications/create.turbo_stream.erb` | Turbo Streamsで公開設定UIを更新 |
| 4-2 | destroy.turbo_stream.erb | `app/views/boards/publications/destroy.turbo_stream.erb` | 非公開時のUI更新 |

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

```
Boards::PublicationsController
    │
    ├─ BoardScoped (concern)
    │      ├─ set_board (Current.user.boards.find)
    │      └─ ensure_permission_to_admin_board
    │
    ├─ create
    │      └─ Board#publish
    │             └─ Board::Publication.create!
    │                    └─ has_secure_token :key
    │
    └─ destroy
           └─ Board#unpublish
                  └─ Board::Publication#destroy
```

### データフロー図

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

POST /boards/:id/publication
         │
         ▼
┌─────────────────┐
│ board_id取得     │ ───▶ Current.user.boards.find
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ 権限チェック      │ ───▶ can_administer_board?
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ publish/unpublish│ ───▶ board_publications (INSERT/DELETE)
└─────────────────┘
         │
         ▼
┌─────────────────┐
│ Turbo Streams   │ ───▶ 公開設定UI更新
└─────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| publications_controller.rb | `app/controllers/boards/publications_controller.rb` | コントローラー | 公開/非公開リクエストの処理 |
| board_scoped.rb | `app/controllers/concerns/board_scoped.rb` | concern | ボード取得と権限チェック |
| publishable.rb | `app/models/board/publishable.rb` | concern | 公開機能のロジック |
| publication.rb | `app/models/board/publication.rb` | モデル | 公開情報の永続化 |
| board.rb | `app/models/board.rb` | モデル | ボードモデル（Publishableをinclude） |
| create.turbo_stream.erb | `app/views/boards/publications/create.turbo_stream.erb` | ビュー | 公開時のUI更新 |
| destroy.turbo_stream.erb | `app/views/boards/publications/destroy.turbo_stream.erb` | ビュー | 非公開時のUI更新 |
| schema.rb | `db/schema.rb` | スキーマ | board_publicationsテーブル定義（144-152行目） |
