# 機能設計書 69-Wikiページ作成・編集

## 概要

本ドキュメントは、GitLabにおけるWikiページ作成・編集機能の設計仕様を記載する。プロジェクトやグループに紐づくWikiドキュメントの作成、更新、削除を行う機能である。

### 本機能の処理概要

Wikiページ作成・編集機能は、GitリポジトリとしてWikiコンテンツを永続化し、バージョン管理を行う機能である。ページの作成・更新・削除操作はGitコミットとして記録され、Webhookや内部イベントとして通知される。

**業務上の目的・背景**：プロジェクトのドキュメンテーションを効率的に作成・管理するためにWiki編集機能は重要である。本機能により、開発者やユーザーはWeb UIからマークアップ形式でドキュメントを作成・編集でき、バージョン管理も自動的に行われる。

**機能の利用シーン**：
- 新規Wikiページの作成
- 既存ページの内容更新
- ページタイトル・パスの変更（リネーム）
- ページの削除
- 画像・ファイルのアップロード（添付）
- テンプレートからのページ作成
- Front Matterによるメタデータ設定

**主要な処理内容**：
1. ページ作成（Wiki#create_page）
2. ページ更新（Wiki#update_page）
3. ページ削除（Wiki#delete_page）
4. 添付ファイル作成（CreateAttachmentService）
5. リダイレクト設定の更新
6. Webhookの実行
7. 内部イベントのトラッキング

**関連システム・外部連携**：
- Gitaly（Wikiリポジトリ操作）
- Webhook（ページ変更通知）
- 内部イベントシステム

**権限による制御**：
- `create_wiki`権限：Wiki作成・編集・削除
- `read_wiki`権限：Wiki閲覧（前提条件）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | Wiki編集画面 | 主機能 | ページ作成・編集 |
| - | Wiki一覧 | 副機能 | ページ一覧からの新規作成 |

## 機能種別

コンテンツ作成 / ドキュメント管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| title | String | Yes | ページタイトル（パス含む） | 最大255バイト |
| content | String | Yes | マークアップコンテンツ | 最大サイズ設定による |
| format | Symbol | No | マークアップ形式 | markdown/rdoc/asciidoc/org |
| message | String | No | コミットメッセージ | - |
| last_commit_sha | String | No | 楽観的ロック用SHA | 更新時のみ |
| front_matter | Hash | No | メタデータ | YAML形式 |

### 入力データソース

- Web UI（フォーム入力）
- API（REST/GraphQL）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page | WikiPage | 作成/更新されたページオブジェクト |
| success | Boolean | 処理成功フラグ |
| message | String | エラーメッセージ（失敗時） |

### 出力先

- Wikiリポジトリ（Gitコミット）
- Webhook（外部通知）
- 内部イベント（メトリクス）

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ 権限チェック（authorize_create_wiki!）
   └─ パラメータ取得

2. バリデーション
   └─ タイトル必須チェック
   └─ パスバイト数チェック
   └─ コンテンツサイズチェック
   └─ 予約スラッグチェック

3. ページ操作
   └─ 【作成】Wiki#create_page
   │      └─ 重複チェック
   │      └─ ファイル作成（Gitaly）
   │      └─ リダイレクト設定更新
   │
   └─ 【更新】Wiki#update_page
   │      └─ 楽観的ロックチェック
   │      └─ ファイル更新（Gitaly）
   │      └─ ディレクトリ移動
   │      └─ リダイレクト設定更新
   │
   └─ 【削除】Wiki#delete_page
          └─ ファイル削除（Gitaly）

4. 後処理
   └─ Webhook実行
   └─ 内部イベント記録
   └─ Eventレコード作成

5. レスポンス返却
   └─ 成功：リダイレクト
   └─ 失敗：エラー表示
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{create_wiki権限?}
    B -->|No| C[403 Forbidden]
    B -->|Yes| D[バリデーション]
    D --> E{バリデーション成功?}
    E -->|No| F[エラー表示]
    E -->|Yes| G{操作種別}
    G -->|作成| H[Wiki#create_page]
    G -->|更新| I{楽観的ロックOK?}
    G -->|削除| J[Wiki#delete_page]
    I -->|No| K[PageChangedError]
    I -->|Yes| L[Wiki#update_page]
    H --> M{成功?}
    L --> M
    J --> M
    M -->|Yes| N[execute_hooks]
    M -->|No| F
    N --> O[内部イベント記録]
    O --> P[リダイレクト]
    K --> F
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | タイトル必須 | タイトルは必須入力 | ページ作成・更新時 |
| BR-02 | タイトルバイト上限 | ファイル名は255バイトまで | ページ作成・更新時 |
| BR-03 | ディレクトリ名バイト上限 | 各ディレクトリ名は255バイトまで | ページ作成・更新時 |
| BR-04 | コンテンツサイズ上限 | wiki_page_max_content_bytes設定による | ページ作成・更新時 |
| BR-05 | 重複タイトル禁止 | 同一タイトルのページは作成不可 | ページ作成時 |
| BR-06 | 予約スラッグ禁止 | pages, templates, new, git_access, - は使用不可 | ページ作成時 |
| BR-07 | 楽観的ロック | last_commit_sha不一致で更新拒否 | ページ更新時 |
| BR-08 | Front Matter上限 | 16KB以下 | Front Matter設定時 |
| BR-09 | 添付ファイル名上限 | 255文字 | ファイルアップロード時 |
| BR-10 | デフォルトコミットメッセージ | "{username} {action} page: {title}" | メッセージ未指定時 |

### イベントアクション一覧

| サービス | internal_event_name | external_action | event_action |
|---------|---------------------|-----------------|--------------|
| CreateService | create_wiki_page | create | :created |
| UpdateService | update_wiki_page | update | :updated |
| DestroyService | delete_wiki_page | delete | :destroyed |

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ページ作成 | wiki_page_meta | INSERT | ページメタ情報作成 |
| ページ作成 | wiki_page_slugs | INSERT | スラッグ登録 |
| ページ更新 | wiki_page_meta | UPDATE | メタ情報更新 |
| ページ削除 | - | - | DBレコード削除なし |
| イベント記録 | events | INSERT | アクティビティ記録 |

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

#### wiki_page_meta

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | title | ページタイトル | 正規化済み |
| INSERT | project_id | プロジェクトID | 外部キー |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------
| - | PageChangedError | 楽観的ロック失敗 | ページを再読み込み |
| - | PageRenameError | リネーム先に既存ページあり | 別のタイトルを指定 |
| - | DuplicatePageError | 同名ページ存在 | 別のタイトルを指定 |
| - | FrontMatterTooLong | Front Matterが16KB超過 | メタデータを削減 |
| - | ValidationError | バリデーション失敗 | 入力値を修正 |
| - | CouldNotCreateWikiError | リポジトリ作成失敗 | 再試行 |

### リトライ仕様

- Git操作失敗時は自動リトライなし
- ユーザーに再操作を促す

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

- Wiki操作はGitコミットとして実行
- DBトランザクションとGit操作は分離

## パフォーマンス要件

- コンテンツサイズ上限: `wiki_page_max_content_bytes`（設定による）
- Front Matter上限: 16KB（`MAX_FRONT_MATTER_LENGTH`）
- 添付ファイル名上限: 255文字

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

- create_wiki権限による操作制御
- 添付ファイル名のサニタイズ処理
- UTF-8以外のエンコーディングは編集不可

## 備考

- 本機能はプロジェクト・グループの両方で利用可能
- feature_category: `wiki`
- Webhookで外部システムに変更通知可能

---

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

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

### 推奨読解順序

#### Step 1: サービス層を理解する

Wikiページ操作のサービスクラスを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | base_service.rb | `app/services/wiki_pages/base_service.rb` | 基底サービス |
| 1-2 | create_service.rb | `app/services/wiki_pages/create_service.rb` | 作成サービス |
| 1-3 | update_service.rb | `app/services/wiki_pages/update_service.rb` | 更新サービス |
| 1-4 | destroy_service.rb | `app/services/wiki_pages/destroy_service.rb` | 削除サービス |

**読解のコツ**:
- **base_service.rb 14-20行目**: execute_hooks - フック実行
- **base_service.rb 43-45行目**: increment_usage - 内部イベント記録
- **base_service.rb 62-74行目**: track_wiki_event - イベントトラッキング
- **create_service.rb 5-15行目**: execute - 作成処理
- **create_service.rb 18-28行目**: イベント名・アクション定義
- **update_service.rb 7-25行目**: execute - 更新処理（楽観的ロック含む）
- **destroy_service.rb 5-12行目**: execute - 削除処理

#### Step 2: モデル層の操作を理解する

Wiki/WikiPageモデルの操作メソッドを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | wiki.rb | `app/models/wiki.rb` | Wikiモデル |
| 2-2 | wiki_page.rb | `app/models/wiki_page.rb` | WikiPageモデル |

**主要処理フロー**:
- **wiki.rb 301-329行目**: create_page - ページ作成
- **wiki.rb 331-360行目**: update_page - ページ更新
- **wiki.rb 362-373行目**: delete_page - ページ削除
- **wiki.rb 470-484行目**: update_redirection_actions - リダイレクト設定
- **wiki.rb 486-496行目**: multi_commit_options - コミットオプション
- **wiki_page.rb 282-288行目**: create - 作成操作
- **wiki_page.rb 302-326行目**: update - 更新操作（楽観的ロック）
- **wiki_page.rb 331-337行目**: delete - 削除操作
- **wiki_page.rb 464-482行目**: validate_path_limits - パスバリデーション
- **wiki_page.rb 484-493行目**: validate_content_size_limit - サイズバリデーション

#### Step 3: コントローラー層を理解する

Webリクエストからの操作を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | wiki_actions.rb | `app/controllers/concerns/wiki_actions.rb` | Wiki共通アクション |

**主要処理フロー**:
- **222-240行目**: update - 更新アクション
- **244-256行目**: create - 作成アクション
- **291-306行目**: destroy - 削除アクション
- **367-369行目**: wiki_params - パラメータ許可リスト

#### Step 4: 添付ファイル処理を理解する

添付ファイルのアップロード処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create_attachment_service.rb | `app/services/wikis/create_attachment_service.rb` | 添付作成サービス |

**主要処理フロー**:
- **5-6行目**: ATTACHMENT_PATH, MAX_FILENAME_LENGTH定義
- **13-21行目**: コンストラクタ - パス生成
- **23-29行目**: create_commit! - コミット作成
- **33-39行目**: clean_file_name - ファイル名サニタイズ
- **51-64行目**: validate! - バリデーション

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

```
HTTP Request (create/update/destroy)
    │
    ├─ WikiActions (Controller)
    │      └─ authorize_create_wiki!
    │      └─ wiki_params
    │
    └─ WikiPages::*Service
           │
           ├─ CreateService#execute
           │      └─ WikiPage#create
           │             └─ Wiki#create_page
           │                    └─ repository.create_file_actions
           │                    └─ repository.commit_files
           │
           ├─ UpdateService#execute
           │      └─ WikiPage#update
           │             └─ Wiki#update_page
           │                    └─ repository.update_file_actions
           │                    └─ repository.commit_files
           │
           └─ DestroyService#execute
                  └─ WikiPage#delete
                         └─ Wiki#delete_page
                                └─ repository.delete_file
```

### データフロー図

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

Form Parameters ────▶ WikiPages::*Service ──────────▶ Wiki Repository
(title, content,           │                              (Git Commit)
 format, message)          │
                           ▼
                    Wiki#create_page
                    Wiki#update_page
                    Wiki#delete_page
                           │
                           ├───────▶ Webhook
                           │        (wiki_page_hooks)
                           │
                           └───────▶ Internal Events
                                    (create_wiki_page, etc.)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| base_service.rb | `app/services/wiki_pages/base_service.rb` | サービス | 基底サービス |
| create_service.rb | `app/services/wiki_pages/create_service.rb` | サービス | 作成サービス |
| update_service.rb | `app/services/wiki_pages/update_service.rb` | サービス | 更新サービス |
| destroy_service.rb | `app/services/wiki_pages/destroy_service.rb` | サービス | 削除サービス |
| create_attachment_service.rb | `app/services/wikis/create_attachment_service.rb` | サービス | 添付作成 |
| wiki.rb | `app/models/wiki.rb` | モデル | Wikiモデル |
| wiki_page.rb | `app/models/wiki_page.rb` | モデル | WikiPageモデル |
| wiki_actions.rb | `app/controllers/concerns/wiki_actions.rb` | Concern | コントローラーアクション |
| wiki_push_service.rb | `app/services/git/wiki_push_service.rb` | サービス | Pushイベント処理 |
