# 機能設計書 40-スタッフ管理

## 概要

本ドキュメントは、Ghostのスタッフ管理機能に関する設計仕様を定義する。この機能により、管理者・編集者などのスタッフユーザーの作成、編集、削除、ロール割り当てを管理できる。

### 本機能の処理概要

スタッフ管理は、Ghostサイトの管理画面にアクセスできるスタッフユーザー（Owner、Administrator、Super Editor、Editor、Author、Contributor）を管理する機能である。ユーザーの作成、プロフィール編集、ロール変更、アカウント削除などの操作が可能。

**業務上の目的・背景**：サイト運営チームを構成するスタッフメンバーを管理し、各メンバーに適切な権限を付与する。ロールベースのアクセス制御により、コンテンツ管理の分業とセキュリティを両立させる。

**機能の利用シーン**：
- 新しいチームメンバーを追加する場合
- スタッフメンバーのロールを変更する場合
- スタッフメンバーのプロフィールを編集する場合
- 退職したメンバーのアカウントを削除する場合
- オーナー権限を別のユーザーに移譲する場合

**主要な処理内容**：
1. スタッフユーザーの一覧表示（browse）
2. 個別ユーザーの取得（read）
3. ユーザー情報の編集（edit）
4. ユーザーの削除（destroy）
5. パスワード変更（changePassword）
6. オーナー権限の移譲（transferOwnership）

**関連システム・外部連携**：
- Roles/Permissions：ロールベースアクセス制御
- 招待機能（Invites）：新規スタッフの招待
- 認証サービス：ログイン・セッション管理

**権限による制御**：
- Owner: 全ての操作が可能、オーナー権限移譲可能
- Administrator: スタッフ管理可能（オーナー以外）
- Editor/Super Editor: 自分自身のプロフィール編集、Author/Contributorの編集・削除
- Author/Contributor: 自分自身のプロフィール編集のみ

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 36 | ユーザー・権限設定 | 主画面 | スタッフユーザーの一覧・管理 |

## 機能種別

CRUD操作 / ユーザー管理

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| name | String | Yes | ユーザー名 | 空でないこと |
| email | String | Yes | メールアドレス | メール形式、一意 |
| password | String | No | パスワード | セキュリティ要件を満たすこと |
| slug | String | No | URLスラッグ | 一意 |
| profile_image | String | No | プロフィール画像URL | URL形式 |
| cover_image | String | No | カバー画像URL | URL形式 |
| bio | String | No | 自己紹介 | 最大200文字 |
| website | String | No | WebサイトURL | URL形式 |
| location | String | No | 所在地 | 最大150文字 |
| facebook | String | No | Facebookアカウント | 最大2000文字 |
| twitter | String | No | Twitterアカウント | 最大2000文字 |
| roles | Array | No | 割り当てるロール | 1つのロールのみ |

### 入力データソース

- 管理画面（Ghost Admin > Staff）
- Users API

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | String | ユーザーID |
| name | String | ユーザー名 |
| email | String | メールアドレス |
| slug | String | URLスラッグ |
| profile_image | String | プロフィール画像URL |
| cover_image | String | カバー画像URL |
| bio | String | 自己紹介 |
| status | String | ステータス（active, inactive, locked等） |
| roles | Array | 割り当てられたロール |
| last_seen | DateTime | 最終アクセス日時 |

### 出力先

- usersテーブル
- Admin APIレスポンス

## 処理フロー

### 処理シーケンス

```
1. ユーザー一覧取得（browse）
   └─ Users API browse呼び出し
   └─ User.findPage()でページング取得
   └─ ロール情報と共に返却

2. ユーザー編集（edit）
   └─ 権限チェック（permissible）
   └─ メールアドレス重複チェック
   └─ User.edit()でDB更新
   └─ ロール変更がある場合は別途処理
   └─ キャッシュ無効化（publicAttrs変更時）

3. ユーザー削除（destroy）
   └─ 権限チェック（Ownerは削除不可）
   └─ 関連データのクリーンアップ
   └─ User.destroy()でDB削除
```

### フローチャート

```mermaid
flowchart TD
    A[ユーザー編集リクエスト] --> B{権限チェック}
    B -->|不可| C[NoPermissionError]
    B -->|可| D{自分自身?}
    D -->|Yes| E[プロフィール編集可]
    D -->|No| F{対象ユーザーのロール?}
    F -->|Owner| G{編集者がOwner?}
    G -->|Yes| H[編集可]
    G -->|No| C
    F -->|その他| I{Editor権限でAuthor/Contributor?}
    I -->|Yes| H
    I -->|No| J{Admin権限?}
    J -->|Yes| H
    J -->|No| C
    E --> K[User.edit()]
    H --> K
    K --> L{ロール変更あり?}
    L -->|Yes| M[ロール更新処理]
    L -->|No| N[完了]
    M --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-40-01 | 1ロール制限 | 1ユーザーに1ロールのみ割り当て可能 | ロール割り当て時 |
| BR-40-02 | Owner削除不可 | Ownerロールのユーザーは削除できない | ユーザー削除時 |
| BR-40-03 | 自己ステータス変更不可 | 自分自身のステータスをinactive/lockedに変更不可 | ステータス変更時 |
| BR-40-04 | 自己ロール変更不可 | 自分自身のロールは変更不可 | ロール変更時 |
| BR-40-05 | Owner移譲制限 | Ownerのみがオーナー権限を移譲可能 | transferOwnership |
| BR-40-06 | Active Admin制限 | オーナー権限を受け取れるのはActiveなAdminのみ | transferOwnership |
| BR-40-07 | パスワードハッシュ化 | パスワードはbcryptでハッシュ化して保存 | ユーザー作成・パスワード変更時 |
| BR-40-08 | Gravatar連携 | メールアドレス変更時にGravatarを自動取得 | メール変更時 |

### 計算ロジック

- スラッグ生成: ユーザー名からURLフレンドリーなスラッグを自動生成
- パスワードハッシュ: `security.password.hash(password)`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ユーザー取得 | users | SELECT | ユーザー情報の取得 |
| ユーザー編集 | users | UPDATE | ユーザー情報の更新 |
| ユーザー削除 | users | DELETE | ユーザーの削除 |
| ロール割り当て | roles_users | INSERT/UPDATE/DELETE | ロールの関連付け |

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

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | id/email/slugで検索、ロール付きで取得 | withRelated: ['roles'] |
| UPDATE | name, email, bio等 | ユーザー入力値 | パスワードはハッシュ化 |
| DELETE | - | id = {user_id} | Ownerは削除不可 |

#### roles_users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | role_id | user_id = {user_id} | ユーザーのロール取得 |
| UPDATE | role_id | 新しいロールID | ロール変更時 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ValidationError | 名前が空 | 「Value in [users.name] cannot be blank」 |
| - | ValidationError | 複数ロール指定 | 「Only one role per user is supported」 |
| - | ValidationError | パスワード要件不足 | パスワード要件エラーメッセージ |
| - | NotFoundError | ユーザーが見つからない | 「User not found」 |
| - | NoPermissionError | 権限不足 | 「You do not have permission」 |
| - | ValidationError | メール重複 | 「Email is already in use」 |

### リトライ仕様

リトライは不要。

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

- ユーザー削除時はトランザクション内でroles_usersの関連も削除
- ロール変更時はupdatePivotで関連テーブルを更新

## パフォーマンス要件

- ユーザー一覧はページング対応（findPage）
- メールアドレス検索は全件取得後にJS処理（SQLiteのunicode対応のため）

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

- パスワードはbcryptでハッシュ化
- パスワード変更時は他セッションを無効化
- toJSON()でパスワードハッシュを除外
- 権限チェックはpermissibleメソッドで厳密に実行

## 備考

- ユーザーステータス: active, warn-1〜4, inactive, locked
- Ownerは1人のみ存在可能
- インポート時はパスワードをランダム値に置換し、ステータスをlockedに設定

---

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

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

### 推奨読解順序

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

まず、ユーザーモデルのデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.js | `ghost/core/core/server/data/schema/schema.js` | **90-145行目**: usersテーブルのスキーマ定義 |
| 1-2 | user.js | `ghost/core/core/server/models/user.js` | **14-15行目**: ASSIGNABLE_ROLES定義、**55-76行目**: defaults定義 |

**読解のコツ**:
- activeStates/inactiveStatesでステータス管理を確認
- ASSIGNABLE_ROLESで割り当て可能なロールを確認

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

Userモデルの主要メソッドを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | user.js | `ghost/core/core/server/models/user.js` | **183-287行目**: onSaving（パスワードハッシュ、スラッグ生成、Gravatar取得） |
| 2-2 | user.js | `ghost/core/core/server/models/user.js` | **534-619行目**: edit（ユーザー編集、ロール変更） |
| 2-3 | user.js | `ghost/core/core/server/models/user.js` | **823-975行目**: permissible（権限チェック） |

**主要処理フロー**:
1. **183-287行目**: onSaving - 名前空チェック、Gravatar取得、スラッグ生成、パスワードハッシュ化
2. **534-619行目**: edit - 重複メールチェック、モデル更新、ロール変更処理
3. **823-975行目**: permissible - Owner/Editor/Adminの権限判定

#### Step 3: API エンドポイントを理解する

Users APIの実装を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | users.js | `ghost/core/core/server/api/endpoints/users.js` | **84-294行目**: browse/read/edit/destroy/changePassword/transferOwnership |

**主要処理フロー**:
1. **87-111行目**: browse - ユーザー一覧取得
2. **149-184行目**: edit - ユーザー編集、キャッシュ無効化判定
3. **186-210行目**: destroy - ユーザー削除

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

```
Users API (browse/read/edit/destroy)
    │
    ├─ browse
    │      └─ User.findPage(options)
    │             └─ users table SELECT
    │
    ├─ edit
    │      ├─ User.permissible() (権限チェック)
    │      │
    │      └─ User.edit(data, options)
    │             ├─ 重複メールチェック
    │             │      └─ getByEmail()
    │             │
    │             ├─ ghostBookshelf.Model.edit()
    │             │      └─ users table UPDATE
    │             │
    │             └─ roles().updatePivot() (ロール変更)
    │                    └─ roles_users table UPDATE
    │
    └─ destroy
           ├─ User.permissible() (Ownerチェック)
           │
           └─ userService.destroyUser()
                  └─ User.destroy()
                         ├─ roles_users DELETE
                         └─ users DELETE

パスワード変更
    │
    └─ User.changePassword()
           ├─ isPasswordCorrect() (旧パスワード検証)
           │
           ├─ user.save({password}) (新パスワード保存)
           │
           └─ session.destroy() (他セッション無効化)

オーナー移譲
    │
    └─ User.transferOwnership()
           ├─ 現在のOwner検証
           │
           ├─ 対象ユーザー検証 (Active Admin)
           │
           └─ roles().updatePivot() (双方のロール変更)
```

### データフロー図

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

管理画面入力 ───────▶ Users API edit
                              │
                              ▼
                        permissible()
                              │
                              ▼
                        User.edit()
                              │
                              ├─▶ onSaving
                              │      ├─ パスワードハッシュ化
                              │      ├─ スラッグ生成
                              │      └─ Gravatar取得
                              │
                              ├─▶ users table UPDATE
                              │
                              └─▶ roles_users UPDATE (ロール変更時)
                                        │
                                        ▼
                              キャッシュ無効化 (publicAttrs変更時)
                                        │
                                        ▼
                              API レスポンス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| user.js | `ghost/core/core/server/models/user.js` | ソース | Userモデル |
| users.js | `ghost/core/core/server/api/endpoints/users.js` | ソース | Users APIエンドポイント |
| users.js | `ghost/core/core/server/services/users.js` | ソース | Usersサービス |
| schema.js | `ghost/core/core/server/data/schema/schema.js` | 設定 | usersテーブルスキーマ |
| role.js | `ghost/core/core/server/models/role.js` | ソース | Roleモデル |
| role-utils.js | `ghost/core/core/server/models/role-utils.js` | ソース | ロール判定ユーティリティ |
| validate-password.js | `ghost/core/core/server/lib/validate-password.js` | ソース | パスワードバリデーション |
