# 機能設計書 11-メンバーラベル

## 概要

本ドキュメントは、Ghostにおけるメンバーラベル機能の設計仕様を記載する。メンバーラベル機能は、サイトのメンバー（購読者）を分類・管理するためのタグ付けシステムを提供する。

### 本機能の処理概要

**業務上の目的・背景**：
メンバー（サイト購読者）の管理において、特定の属性や興味関心によってメンバーをグループ化する必要がある。例えば、VIP会員、特定イベント参加者、特定トピックに興味のあるメンバーなど、様々な観点での分類が求められる。この機能により、管理者はメンバーを柔軟にセグメント化し、ターゲットを絞ったコミュニケーションやコンテンツ配信が可能となる。

**機能の利用シーン**：
- 管理画面でメンバーにラベルを付与・削除する場合
- CSVインポート時に自動的にラベルを付与する場合
- ニュースレター配信時に特定ラベルを持つメンバーにのみ配信する場合
- メンバー一覧をラベルでフィルタリングして表示する場合

**主要な処理内容**：
1. ラベルの作成：名前とスラッグを指定して新規ラベルを作成
2. ラベルの取得：一覧取得または個別取得（メンバー数カウント含む）
3. ラベルの更新：ラベル名やスラッグを変更
4. ラベルの削除：ラベルとメンバーとの関連を解除後、ラベルを削除
5. メンバーへのラベル付与：多対多リレーションによるラベルの関連付け

**関連システム・外部連携**：
- メンバー管理システム（members_labelsテーブルを介した関連）
- CSVインポート機能（インポート時のラベル自動付与）

**権限による制御**：
- Author以上のロールでラベルの参照・追加・編集・削除が可能（5.0で追加された権限）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 17 | メンバー一覧画面 | 参照画面 | ラベルによるフィルタリング |
| 18 | メンバー詳細画面 | 主画面 | メンバーへのラベル付与・削除 |

## 機能種別

CRUD操作 / データ分類

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | string | Yes | ラベル名 | 空白不可、トリム処理適用 |
| slug | string | No | URLスラッグ | 自動生成可能、ユニーク制約 |

### 入力データソース

- 管理画面からのAPI呼び出し
- CSVインポート処理からの内部呼び出し

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | ラベルのUUID |
| name | string | ラベル名 |
| slug | string | URLスラッグ |
| created_at | datetime | 作成日時 |
| updated_at | datetime | 更新日時 |
| count.members | number | 関連するメンバー数（オプション） |

### 出力先

- REST API レスポンス（JSON形式）

## 処理フロー

### 処理シーケンス

```
1. APIリクエスト受信
   └─ ラベルエンドポイントでリクエストを受け付け
2. パーミッションチェック
   └─ ユーザーの権限を確認
3. バリデーション
   └─ 入力パラメータの検証
4. ビジネスロジック実行
   └─ Bookshelfモデルを介したデータ操作
5. イベント発火
   └─ label.added / label.edited / label.deleted イベント
6. レスポンス返却
   └─ JSON形式でクライアントに返却
```

### フローチャート

```mermaid
flowchart TD
    A[APIリクエスト受信] --> B{権限チェック}
    B -->|許可| C[バリデーション]
    B -->|拒否| D[403エラー返却]
    C -->|成功| E{操作種別}
    C -->|失敗| F[400エラー返却]
    E -->|作成| G[Label.add実行]
    E -->|取得| H[Label.findOne/findPage実行]
    E -->|更新| I[Label.edit実行]
    E -->|削除| J[Label.destroy実行]
    G --> K[イベント発火]
    H --> L[レスポンス返却]
    I --> K
    J --> K
    K --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-11-01 | ラベル名トリミング | ラベル名の前後の空白を自動的に除去する | ラベル作成・更新時 |
| BR-11-02 | スラッグ自動生成 | slugが未指定の場合、nameからslugを自動生成する | ラベル作成時 |
| BR-11-03 | ユニーク制約 | 同一slugのラベルは作成不可 | ラベル作成時 |
| BR-11-04 | 削除時の関連解除 | ラベル削除時、全メンバーとの関連を先に解除する | ラベル削除時 |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ラベル作成 | labels | INSERT | 新規ラベルレコード追加 |
| ラベル取得 | labels | SELECT | ラベル情報取得 |
| ラベル更新 | labels | UPDATE | ラベル情報更新 |
| ラベル削除 | members_labels | DELETE | メンバーとの関連解除 |
| ラベル削除 | labels | DELETE | ラベルレコード削除 |
| メンバー数カウント | members_labels, members | SELECT | 関連メンバー数集計 |

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

#### labels

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 新規UUID | 自動生成 |
| INSERT | name | 入力値（トリム済） | 必須 |
| INSERT | slug | 入力値または自動生成値 | ユニーク |
| INSERT | created_at | 現在日時 | 自動設定 |
| INSERT | updated_at | 現在日時 | 自動設定 |
| UPDATE | name | 入力値（トリム済） | 変更時のみ |
| UPDATE | slug | 入力値 | 変更時のみ |
| UPDATE | updated_at | 現在日時 | 自動更新 |

#### members_labels

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | label_id | 削除対象ラベルID | ラベル削除前処理 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| NotFoundError | 404 | 指定IDのラベルが存在しない | 正しいIDを指定 |
| ValidationError | 400 | 同一slugが既に存在 | 別の名前・slugを使用 |
| ValidationError | 400 | ラベル名が空 | 有効なラベル名を入力 |

### リトライ仕様

特になし（同期処理のため）

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

- ラベル削除時: members_labels からの関連解除と labels からのレコード削除を単一トランザクションで実行

## パフォーマンス要件

- ラベル一覧取得: ページネーション対応（デフォルト15件/ページ）
- メンバー数カウント: 専用のサブクエリで効率的に集計

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

- 認証必須: Admin APIとしてアクセストークンが必要
- 権限チェック: Author以上のロールが必要
- 入力サニタイズ: ラベル名のトリム処理

## 備考

- ラベルはメンバーセグメンテーションの基本単位
- CSVインポート時に自動生成されるインポートラベルは日時付きの名前が設定される

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | label.js | `ghost/core/core/server/models/label.js` | Labelモデルの定義、テーブル名、リレーション |

**読解のコツ**:
- `ghostBookshelf.Model.extend`でBookshelfモデルを定義
- `tableName: 'labels'`でDBテーブル名を指定
- `members`メソッドで多対多リレーションを定義（belongsToMany）
- `onSaving`でスラッグ自動生成ロジックを確認

**主要処理フロー**:
- **12-66行目**: Labelモデル定義（テーブル名、イベント発火、onSaving処理）
- **59-61行目**: membersリレーション定義（members_labels中間テーブル経由）
- **93-105行目**: countRelationsでメンバー数カウントのサブクエリ定義
- **107-126行目**: destroyメソッドで関連解除後にラベル削除

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | labels.js | `ghost/core/core/server/api/endpoints/labels.js` | APIエンドポイント定義、CRUDハンドラ |

**主要処理フロー**:
- **16-38行目**: browse - ラベル一覧取得（ページネーション対応）
- **41-71行目**: read - 個別ラベル取得
- **74-100行目**: add - ラベル作成（ユニーク制約エラーハンドリング含む）
- **102-135行目**: edit - ラベル更新
- **137-159行目**: destroy - ラベル削除

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | labels.js | `ghost/core/core/server/api/endpoints/utils/validators/input/labels.js` | 入力バリデーション |

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

```
labels.js (API Endpoint)
    │
    ├─ browse()
    │      └─ models.Label.findPage()
    │
    ├─ read()
    │      └─ models.Label.findOne()
    │
    ├─ add()
    │      └─ models.Label.add()
    │             └─ onSaving() [slug生成]
    │             └─ onCreated() [イベント発火]
    │
    ├─ edit()
    │      └─ models.Label.edit()
    │             └─ onSaving() [slug生成]
    │             └─ onUpdated() [イベント発火]
    │
    └─ destroy()
           └─ models.Label.destroy()
                  └─ members.detach() [関連解除]
                  └─ onDestroyed() [イベント発火]
```

### データフロー図

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

クライアント          labels.js           label.js          DB
リクエスト ────▶ (API Endpoint) ────▶ (Bookshelf Model) ────▶ labels
   │                   │                    │               members_labels
   │                   │                    │
   └── JSON ────────── レスポンス生成 ◀───── 結果 ◀────────────┘
       Body                                toJSON()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| label.js | `ghost/core/core/server/models/label.js` | ソース | Labelモデル定義 |
| labels.js | `ghost/core/core/server/api/endpoints/labels.js` | ソース | API エンドポイント |
| labels.js | `ghost/core/core/server/api/endpoints/utils/validators/input/labels.js` | ソース | 入力バリデーション |
| labels-importer.js | `ghost/core/core/server/data/seeders/importers/labels-importer.js` | ソース | シーダーインポーター |
| members-labels-importer.js | `ghost/core/core/server/data/seeders/importers/members-labels-importer.js` | ソース | メンバー-ラベル関連インポーター |
| labels.js | `ghost/core/core/server/services/members/importer/labels.js` | ソース | CSVインポート時のラベル処理 |
