# 画面設計書 192-プロフィール編集

## 概要

本ドキュメントは、ユーザープロフィール編集画面の設計書である。ユーザーが自身のプロフィール情報を閲覧・編集するための画面仕様を定義する。

### 本画面の処理概要

ログインユーザーが自身のプロフィール情報を編集するための画面である。アバター画像、表示名、ステータス、タイムゾーン、SNSアカウント連携など、多岐にわたる個人情報の設定が可能である。

**業務上の目的・背景**：GitLabはチームコラボレーションツールであり、ユーザー間のコミュニケーションを円滑にするためにプロフィール情報が重要な役割を果たす。名前、アバター、連絡先情報を適切に設定することで、プロジェクトメンバー間の認識齟齬を防ぎ、効率的なコミュニケーションを実現する。また、タイムゾーン設定は分散チームでの作業において時間調整を容易にする。

**画面へのアクセス方法**：
1. 右上のユーザーアバターをクリック
2. 「プロフィール編集」を選択
3. または URL `/-/user_settings/profile` に直接アクセス

**主要な操作・処理内容**：
1. アバター画像のアップロード・削除・トリミング
2. ステータス（絵文字・メッセージ・可用性）の設定
3. タイムゾーンの設定
4. 基本情報（名前、代名詞、発音、Webサイト、所在地、職種、所属組織、自己紹介）の編集
5. SNSアカウント（LinkedIn、X/Twitter、Discord、Bluesky、Mastodon、GitHub、ORCID）の連携設定
6. プライバシー設定（プライベートプロフィール、プライベート貢献の表示、アチーブメント表示）

**画面遷移**：
- 遷移元: ユーザーメニュー、ユーザープロフィール画面
- 遷移先: ユーザープロフィール画面（キャンセル時）

**権限による表示制御**：
- ログイン必須
- LDAPユーザーの場合、一部フィールド（name等）が読み取り専用になる
- 外部プロバイダ（SAML等）で管理されるフィールドは読み取り専用

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 77 | ユーザープロファイル | 主機能 | ユーザープロフィールの編集 |
| 144 | アバター管理 | 補助機能 | アバター画像のアップロード・削除 |

## 画面種別

編集

## URL/ルーティング

```
GET  /-/user_settings/profile
PUT  /-/user_settings/profile
```

## 入出力項目

| 項目名 | 項目ID | 入出力 | 型 | 必須 | 説明 |
|--------|--------|--------|-----|------|------|
| アバター | avatar | 入力 | file | No | 最大200KB、推奨192x192px |
| ステータス絵文字 | status[emoji] | 入力 | string | No | 絵文字コード |
| ステータスメッセージ | status[message] | 入力 | string | No | 表示メッセージ |
| 可用性 | status[availability] | 入力 | string | No | busy等 |
| ステータスクリア時間 | status[clear_status_after] | 入力 | string | No | 自動クリア設定 |
| タイムゾーン | timezone | 入力 | string | No | タイムゾーン識別子 |
| 名前 | name | 入力 | string | Yes | 表示名 |
| 代名詞 | pronouns | 入力 | string | No | 代名詞 |
| 発音 | pronunciation | 入力 | string | No | 名前の発音 |
| WebサイトURL | website_url | 入力 | string | No | 個人Webサイト |
| 所在地 | location | 入力 | string | No | 居住地 |
| 職種 | job_title | 入力 | string | No | 職種 |
| 所属組織 | user_detail_organization | 入力 | string | No | 所属組織名 |
| 自己紹介 | bio | 入力 | text | No | 最大250文字 |
| LinkedIn | linkedin | 入力 | string | No | LinkedInプロファイル名 |
| X (Twitter) | twitter | 入力 | string | No | @username形式 |
| Discord | discord | 入力 | string | No | ユーザーID（17-20桁） |
| Bluesky | bluesky | 入力 | string | No | DID形式 |
| Mastodon | mastodon | 入力 | string | No | @username@server形式 |
| GitHub | github | 入力 | string | No | ユーザー名 |
| ORCID | orcid | 入力 | string | No | ORCID ID |
| プライベートプロフィール | private_profile | 入力 | boolean | No | プロフィール非公開 |
| プライベート貢献を含む | include_private_contributions | 入力 | boolean | No | 非公開貢献の表示 |
| アチーブメント表示 | achievements_enabled | 入力 | boolean | No | アチーブメント表示 |

## 表示項目

| 項目名 | データソース | 説明 |
|--------|-------------|------|
| 現在のアバター | @user.avatar | アバター画像URL |
| ユーザーID | @user.id | 読み取り専用 |
| Gravatar設定 | Gitlab.config.gravatar.enabled | Gravatar使用可否 |
| 読み取り専用フィールド表示 | @user.read_only_attribute? | 外部プロバイダ管理フィールド |

## イベント仕様

### 1-プロフィール更新

**トリガー**: 「Update profile settings」ボタン押下

**処理フロー**:
1. フォームデータをPUTリクエストで送信
2. `Users::UpdateService` でユーザー情報を更新
3. `check_password: true` オプションにより、必要に応じてパスワード確認
4. 成功時: 成功メッセージを表示
5. 失敗時: エラーメッセージを表示

### 2-アバターアップロード

**トリガー**: 「Choose file...」ボタンでファイル選択

**処理フロー**:
1. ファイル選択ダイアログを表示
2. 画像を選択
3. トリミングモーダルを表示（クロップ調整）
4. 「Set new profile picture」で確定
5. フォーム送信時にアップロード処理

### 3-アバター削除

**トリガー**: 「Remove avatar」ボタン押下

**処理フロー**:
1. 確認ダイアログを表示
2. 確認後、DELETE `/profile/avatar` へリクエスト
3. アバターを削除（Gravatarが有効な場合はGravatarに戻る）

### 4-ステータス設定

**トリガー**: ステータスフォームの入力

**処理フロー**:
1. Vue.jsコンポーネント（`#js-user-profile-set-status-form`）でステータスを入力
2. hidden fieldに値が設定される
3. フォーム送信時に一緒に保存

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| プロフィール更新 | users | UPDATE | ユーザー基本情報の更新 |
| プロフィール更新 | user_details | UPDATE | 詳細情報の更新 |
| プロフィール更新 | user_statuses | UPSERT | ステータス情報の更新 |
| アバター削除 | uploads | DELETE | アップロードファイルの削除 |

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

#### users

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | name | フォーム入力値 | 表示名 |
| UPDATE | avatar | アップロードファイル | アバター画像 |
| UPDATE | website_url | フォーム入力値 | WebサイトURL |
| UPDATE | location | フォーム入力値 | 所在地 |
| UPDATE | linkedin | フォーム入力値 | LinkedIn |
| UPDATE | twitter | フォーム入力値 | X/Twitter |
| UPDATE | bio | フォーム入力値 | 自己紹介 |
| UPDATE | timezone | フォーム入力値 | タイムゾーン |
| UPDATE | private_profile | フォーム入力値 | プライベート設定 |
| UPDATE | include_private_contributions | フォーム入力値 | 貢献表示設定 |
| UPDATE | updated_at | 現在日時 | 更新日時 |

#### user_details

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | pronouns | フォーム入力値 | 代名詞 |
| UPDATE | pronunciation | フォーム入力値 | 発音 |
| UPDATE | job_title | フォーム入力値 | 職種 |
| UPDATE | organization | フォーム入力値 | 所属組織 |
| UPDATE | discord | フォーム入力値 | Discord ID |
| UPDATE | bluesky | フォーム入力値 | Bluesky |
| UPDATE | mastodon | フォーム入力値 | Mastodon |
| UPDATE | github | フォーム入力値 | GitHub |
| UPDATE | orcid | フォーム入力値 | ORCID |

#### user_statuses

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPSERT | emoji | フォーム入力値 | ステータス絵文字 |
| UPSERT | message | フォーム入力値 | ステータスメッセージ |
| UPSERT | availability | フォーム入力値 | 可用性 |
| UPSERT | clear_status_at | 計算値 | 自動クリア日時 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|--------------|---------|
| MSG-001 | 成功 | Profile was successfully updated | 更新成功時 |
| MSG-002 | 警告 | Avatar will be removed. Are you sure? | アバター削除確認 |
| MSG-003 | エラー | You are not permitted to change email OTP enrollment | Email OTP変更不可時 |
| MSG-004 | エラー | You must provide a valid current password. | パスワード検証失敗時 |

## 例外処理

| 例外 | 発生条件 | 対応処理 |
|-----|---------|---------|
| 未認証 | ログインしていない | ログイン画面へリダイレクト |
| バリデーションエラー | 入力値が不正 | エラーメッセージを表示 |
| ファイルサイズ超過 | アバターが200KB超 | エラーメッセージを表示 |

## 備考

- Feature Flag `edit_user_profile_vue` が有効な場合、Vue.jsコンポーネントによるレンダリングに切り替わる
- LDAPユーザーは一部フィールドが読み取り専用となる
- アバター画像は200KB以下、推奨サイズは192x192ピクセル
- Discord IDは17〜20桁の数字である必要がある

---

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

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

### 推奨読解順序

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

ユーザー関連のデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | user.rb | `app/models/user.rb` | ユーザーモデルの属性・関連を確認 |
| 1-2 | user_detail.rb | `app/models/user_detail.rb` | 詳細情報の属性を確認 |
| 1-3 | user_status.rb | `app/models/user_status.rb` | ステータス情報の属性を確認 |

**読解のコツ**: `User` モデルは `has_one :user_detail` と `has_one :status` の関連を持つ。プロフィール編集では3つのモデルが関連する。

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

処理の起点となるコントローラーを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | profiles_controller.rb | `app/controllers/user_settings/profiles_controller.rb` | show/updateアクションの実装を確認 |

**主要処理フロー**:
1. **17行目**: `show` アクション - 編集画面の表示
2. **19-40行目**: `update` アクション - ユーザー情報の更新処理
3. **48-81行目**: `user_params_attributes` - 許可されたパラメータ一覧

#### Step 3: ビューレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | show.html.haml | `app/views/user_settings/profiles/show.html.haml` | フォーム構造を確認 |

**主要処理フロー**:
- **7-8行目**: Feature Flag による Vue.js / HAML の切り替え
- **10行目**: `gitlab_ui_form_for` でフォーム生成
- **11-45行目**: アバターセクション
- **47-59行目**: ステータスセクション
- **61-67行目**: タイムゾーンセクション
- **69-178行目**: メイン設定セクション（名前、SNS等）

#### Step 4: サービスレイヤーを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | update_service.rb | `app/services/users/update_service.rb` | ユーザー更新ロジック |

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

```
UserSettings::ProfilesController#show
    │
    └─ render 'show'
           ├─ gitlab_ui_form_for @user
           │      ├─ SettingsSectionComponent (アバター)
           │      ├─ SettingsSectionComponent (ステータス)
           │      │      └─ #js-user-profile-set-status-form (Vue)
           │      ├─ SettingsSectionComponent (タイムゾーン)
           │      │      └─ .js-timezone-dropdown (Vue)
           │      └─ SettingsSectionComponent (メイン設定)
           │
           └─ #password-prompt-modal

UserSettings::ProfilesController#update
    │
    ├─ validate_email_otp_preference_modification
    │
    └─ Users::UpdateService#execute
           ├─ User#update
           ├─ UserDetail#update
           └─ UserStatus#upsert
```

### データフロー図

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

フォームデータ ───▶ ProfilesController#update ───▶ 更新結果
    │                      │
    │                      ├─ Users::UpdateService
    │                      │      ├─ User.update
    │                      │      ├─ UserDetail.update
    │                      │      └─ UserStatus.upsert
    │                      │
    │                      └─ check_password (オプション)
    │
アバターファイル ───▶ CarrierWave ───▶ uploads テーブル
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| profiles_controller.rb | `app/controllers/user_settings/profiles_controller.rb` | コントローラー | リクエスト処理 |
| show.html.haml | `app/views/user_settings/profiles/show.html.haml` | テンプレート | ビューレンダリング |
| user.rb | `app/models/user.rb` | モデル | ユーザーデータ定義 |
| user_detail.rb | `app/models/user_detail.rb` | モデル | 詳細情報定義 |
| user_status.rb | `app/models/user_status.rb` | モデル | ステータス情報定義 |
| update_service.rb | `app/services/users/update_service.rb` | サービス | ユーザー更新ロジック |
| user_settings.rb | `config/routes/user_settings.rb` | 設定 | ルーティング定義 |
| _name.html.haml | `app/views/user_settings/profiles/_name.html.haml` | パーシャル | 名前フィールド |
