# 画面設計書 36-ユーザー・権限設定

## 概要

本ドキュメントは、Ghost管理画面の「ユーザー・権限設定」（Staff）の設計内容を記載した画面設計書です。この画面は、スタッフユーザーの一覧表示、招待、権限管理を行うための機能を提供します。

### 本画面の処理概要

この画面では、Ghostサイトのスタッフユーザー（管理者、編集者、投稿者、寄稿者）の管理および新規招待を行うことができます。

**業務上の目的・背景**：複数人でサイトを運営する場合、適切な権限管理が不可欠です。この画面でスタッフの追加・削除、ロール変更、招待の管理、セキュリティ設定（2FA必須化）を行うことで、チーム運営とセキュリティの両立を実現します。

**画面へのアクセス方法**：
- 設定画面（/ghost/settings）のGeneral settingsセクション内
- 「Staff」項目でユーザー一覧を確認

**主要な操作・処理内容**：
1. オーナー情報の確認：サイトオーナーのプロフィール表示
2. ロール別ユーザー一覧：タブでロール（Administrators, Editors, Authors, Contributors, Invited）を切り替え
3. ユーザー詳細表示：ユーザーをクリックして詳細モーダルを表示
4. ユーザー招待：「Invite people」ボタンで新規招待モーダルを表示
5. 招待の管理：招待の取り消し（Revoke）、再送信（Resend）
6. セキュリティ設定：2FA（メール認証コード）の必須化トグル

**画面遷移**：
- 遷移元：設定画面のGeneral settingsセクション
- 遷移先：UserDetailModal（ユーザー詳細）、InviteUserModal（招待モーダル）

**権限による表示制御**：
- Admin以上：全ユーザーの編集、招待管理、セキュリティ設定が可能
- Editor：自分とContributorのみ編集可能
- 自分自身のプロフィールは常に編集可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 40 | スタッフ管理 | 主機能 | スタッフユーザーの一覧・管理 |
| 41 | ロール管理 | 補助機能 | ユーザーロールの表示 |
| 43 | 招待機能 | 補助機能 | 新規スタッフの招待 |

## 画面種別

一覧（タブ切り替え形式）

## URL/ルーティング

| URL パターン | 説明 |
|-------------|------|
| `/ghost/settings` (General settings内) | 設定画面内のStaffセクション |
| `/ghost/settings/staff?tab={tabId}` | タブ指定付き |
| `/ghost/settings/staff/{slug}` | ユーザー詳細モーダル表示 |
| `/ghost/settings/staff/invite` | 招待モーダル表示 |

## 入出力項目

### 表示用データ

| 項目名 | 型 | データソース | 説明 |
|--------|-----|-------------|------|
| ownerUser | User | useStaffUsers() | サイトオーナー |
| adminUsers | User[] | useStaffUsers() | 管理者一覧 |
| editorUsers | User[] | useStaffUsers() | 編集者一覧 |
| authorUsers | User[] | useStaffUsers() | 投稿者一覧 |
| contributorUsers | User[] | useStaffUsers() | 寄稿者一覧 |
| invites | UserInvite[] | useStaffUsers() | 招待一覧 |
| totalUsers | number | useStaffUsers() | 総ユーザー数 |
| totalInvites | number | useStaffUsers() | 総招待数 |

## 表示項目

### オーナーセクション

| 項目名 | コンポーネント | 説明 |
|--------|--------------|------|
| アバター | Avatar | ユーザー画像またはイニシャル |
| 名前 + ロール | テキスト | "{name} - Owner" |
| メールアドレス | テキスト | サブテキストとして表示 |
| View profileリンク | Button | Admin以上でホバー時に表示 |

### ロール別タブ

| タブID | タブ名 | 内容 | カウンター |
|--------|-------|------|-----------|
| administrators | Administrators | 管理者ユーザー一覧 | adminUsers.length |
| editors | Editors | 編集者ユーザー一覧 | editorUsers.length |
| authors | Authors | 投稿者ユーザー一覧 | authorUsers.length |
| contributors | Contributors | 寄稿者ユーザー一覧 | contributorUsers.length |
| invited | Invited | 招待中ユーザー一覧 | totalInvites |

### ユーザーリスト項目

| 項目名 | 表示内容 | 操作 |
|--------|---------|------|
| アバター | ユーザー画像またはイニシャル | - |
| 名前 | ユーザー名（Suspended付き） | クリックで詳細表示 |
| メールアドレス | detail表示 | - |
| Editボタン | 編集権限がある場合のみ | クリックで詳細表示 |

### 招待リスト項目

| 項目名 | 表示内容 | 操作 |
|--------|---------|------|
| アバター | メールアドレス頭文字 | - |
| メールアドレス | title表示 | - |
| ロール | detail表示 | - |
| Revokeボタン | 招待取り消し | deleteInvite() |
| Resendボタン | 招待再送信 | deleteInvite() + addInvite() |

### セキュリティ設定

| 項目名 | コンポーネント | 表示条件 | 説明 |
|--------|--------------|---------|------|
| Require email 2FA | Toggle | config.security.staffDeviceVerification && Admin以上 | 2FA必須化 |

## イベント仕様

### 1-オーナークリック

**トリガー**: オーナーセクションをクリック（Admin以上）

**処理内容**:
- updateRoute({route: `staff/${user.slug}`})でルート更新
- UserDetailModalが表示される

### 2-ユーザーリスト項目クリック

**トリガー**: ユーザーリストの項目をクリック（編集権限がある場合）

**処理内容**:
- showDetailModal(user)が呼ばれる
- updateRoute({route: `staff/${user.slug}`})でルート更新
- UserDetailModalが表示される

**編集権限判定**:
```javascript
const canEdit = hasAdminAccess(currentUser) ||
    (isEditorUser(currentUser) && isContributorUser(user)) ||
    currentUser.id === user.id;
```

### 3-Invite peopleボタンクリック

**トリガー**: 「Invite people」ボタンをクリック

**処理内容**:
- showInviteModal()が呼ばれる
- updateRoute('staff/invite')でルート更新
- InviteUserModalが表示される

### 4-タブ切り替え

**トリガー**: TabViewでタブを選択

**処理内容**:
- updateSelectedTab(newTab)が呼ばれる
- updateRoute(`staff?tab=${newTab}`)でURLを更新
- setSelectedTab(newTab)で選択状態を更新

### 5-招待取り消し（Revoke）

**トリガー**: 招待リストの「Revoke」ボタンをクリック

**処理内容**:
- setRevokeState('progress')でローディング表示
- deleteInvite(invite.id)でAPI呼び出し
- 成功時: showToast({title: 'Invitation revoked', message: invite.email, type: 'success'})
- 失敗時: handleError(e)

**API呼び出し**:
```
DELETE /ghost/api/admin/invites/{id}
```

### 6-招待再送信（Resend）

**トリガー**: 招待リストの「Resend」ボタンをクリック

**処理内容**:
- setResendState('progress')でローディング表示
- deleteInvite(invite.id)で既存招待を削除
- addInvite({email, roleId})で新規招待を作成
- 成功時: showToast({title: 'Invitation resent', message: invite.email, type: 'success'})
- 失敗時: handleError(e)

**API呼び出し**:
```
DELETE /ghost/api/admin/invites/{id}
POST /ghost/api/admin/invites/
```

### 7-2FA必須化トグル

**トリガー**: 「Require email 2FA」トグルを切り替え

**処理内容**:
- editSettings([{key: 'require_email_mfa', value: !require2fa}])でAPI呼び出し
- 失敗時: handleError(error)

**API呼び出し**:
```
PUT /ghost/api/admin/settings/
{
  "settings": [
    { "key": "require_email_mfa", "value": true/false }
  ]
}
```

### 8-Load moreボタンクリック

**トリガー**: 「Load more」ボタンをクリック

**処理内容**:
- ユーザータブ: fetchNextPage()で次ページ取得
- 招待タブ: fetchNextInvitePage()で次ページ取得

## データベース更新仕様

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示時 | users | SELECT | ユーザー一覧取得 |
| 画面表示時 | invites | SELECT | 招待一覧取得 |
| 招待取り消し | invites | DELETE | 招待レコード削除 |
| 招待再送信 | invites | DELETE + INSERT | 招待の再作成 |
| 2FA設定変更 | settings | UPDATE | require_email_mfa更新 |

### テーブル別更新項目詳細

#### invites

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| DELETE | id | 招待ID | 取り消し/再送信時 |
| INSERT | email | 招待先メールアドレス | 再送信時 |
| INSERT | role_id | ロールID | 再送信時 |

#### settings

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | require_email_mfa | true/false | 2FA必須化設定 |

## メッセージ仕様

| メッセージ種別 | メッセージ内容 | 表示条件 |
|--------------|---------------|---------|
| 成功 | Invitation revoked | 招待取り消し成功 |
| 成功 | Invitation resent | 招待再送信成功 |
| 空状態 | No {groupname} found. | ロールにユーザーがいない場合 |
| 空状態 | No invitations found. | 招待がない場合 |
| ヒント | Require email 2FA codes to be used on all staff logins | 2FA設定の説明 |

## 例外処理

| 例外状態 | 処理内容 |
|---------|---------|
| API通信エラー | handleError()でエラートースト表示 |
| 権限不足 | 編集ボタン非表示、クリック無効化 |
| ページネーション終端 | Load moreボタン非表示 |

## 備考

- useStaffUsersカスタムフックでユーザーと招待のデータを取得
- ユーザーのSuspended状態は名前の後に「(Suspended)」と表示
- アバターの背景色はgenerateAvatarColor関数で名前/メールアドレスから生成
- 2FA設定はconfig.security.staffDeviceVerificationがtrueの場合のみ表示
- ページネーションはInfinite Queryパターンを使用

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | users API | `@tryghost/admin-x-framework/api/users` | User型、hasAdminAccess等のヘルパー関数 |
| 1-2 | invites API | `@tryghost/admin-x-framework/api/invites` | UserInvite型、useAddInvite, useDeleteInvite |

**読解のコツ**: User型にはroles配列が含まれ、hasAdminAccess等でロール判定を行う。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | users.tsx | `apps/admin-x-settings/src/components/settings/general/users.tsx` | メインコンポーネント全体の構造 |

**主要処理フロー**:
1. **L31-54**: Ownerコンポーネント - オーナー情報の表示
2. **L56-102**: UsersListコンポーネント - ロール別ユーザー一覧
3. **L104-169**: UserInviteActionsコンポーネント - 招待の操作ボタン
4. **L171-203**: InvitesUserListコンポーネント - 招待一覧
5. **L205-340**: Usersコンポーネント - メインコンポーネント、タブ管理

#### Step 3: カスタムフックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | use-staff-users.ts | `apps/admin-x-settings/src/hooks/use-staff-users.ts` | ユーザーと招待のデータ取得ロジック |

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

```
[ユーザー操作]
    │
    ├─ Users コンポーネント
    │      │
    │      ├─ useStaffUsers() フック
    │      │      ├─ users, ownerUser, adminUsers, editorUsers, ...
    │      │      ├─ invites
    │      │      ├─ hasNextPage, fetchNextPage
    │      │      └─ invitesHasNextPage, fetchNextInvitePage
    │      │
    │      ├─ useGlobalData()
    │      │      ├─ settings
    │      │      ├─ config
    │      │      └─ currentUser
    │      │
    │      ├─ TopLevelGroup
    │      │      ├─ Owner コンポーネント
    │      │      │      └─ Avatar, クリックでUserDetailModal
    │      │      │
    │      │      ├─ TabView
    │      │      │      ├─ administrators → UsersList
    │      │      │      ├─ editors → UsersList
    │      │      │      ├─ authors → UsersList
    │      │      │      ├─ contributors → UsersList
    │      │      │      └─ invited → InvitesUserList
    │      │      │             └─ UserInviteActions
    │      │      │                    ├─ deleteInvite()
    │      │      │                    └─ addInvite()
    │      │      │
    │      │      └─ Security Settings (Toggle)
    │      │             └─ editSettings()
    │      │
    │      └─ "Invite people" ボタン
    │             └─ InviteUserModal
    │
    └─ [詳細表示時]
           └─ updateRoute('staff/{slug}')
                  └─ UserDetailModal
```

### データフロー図

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

                     ┌─────────────────────────────┐
Ghost Admin API ────▶│ useStaffUsers()             │
GET /users/          │   - ユーザー一覧取得        │
GET /invites/        │   - 招待一覧取得            │
                     │   - ロール別に分類          │
                     └────────────┬────────────────┘
                                  │
                     ┌────────────▼────────────────┐
                     │ Users コンポーネント        │
                     │   - Owner表示               │
                     │   - TabView (ロール別)      │
                     │   - Security Settings       │
                     └────────────┬────────────────┘
                                  │
[クリック] ─────────▶┌────────────▼────────────────┐
                     │ updateRoute()               │───▶ [モーダル表示]
                     │   - staff/{slug}            │     UserDetailModal
                     │   - staff/invite            │     InviteUserModal
                     └─────────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| users.tsx | `apps/admin-x-settings/src/components/settings/general/users.tsx` | ソース | メインコンポーネント |
| use-staff-users.ts | `apps/admin-x-settings/src/hooks/use-staff-users.ts` | ソース | ユーザーデータ取得フック |
| user-detail-modal.tsx | `apps/admin-x-settings/src/components/settings/general/user-detail-modal.tsx` | ソース | ユーザー詳細モーダル |
| invite-user-modal.tsx | `apps/admin-x-settings/src/components/settings/general/invite-user-modal.tsx` | ソース | 招待モーダル |
| helpers.ts | `apps/admin-x-settings/src/utils/helpers.ts` | ソース | generateAvatarColor, getInitials |
| general-settings.tsx | `apps/admin-x-settings/src/components/settings/general/general-settings.tsx` | ソース | 親コンポーネント |
