# 通知設計書 81-group_scheduled_for_deletion

## 概要

本ドキュメントは、GitLabにおける「グループ削除予定通知（group_scheduled_for_deletion）」の設計を定義するものである。グループが削除予定としてマークされた際に、グループのオーナーに対して通知メールを送信する。

### 本通知の処理概要

本通知は、グループが削除予定としてマークされた際に、そのグループのオーナーに対して削除予定であることを通知するメールを送信する機能である。

**業務上の目的・背景**：グループの誤削除を防止し、削除予定のグループについてオーナーに事前通知することで、必要に応じて削除をキャンセルする機会を提供する。これにより、重要なグループやプロジェクトの意図しない削除による業務影響を最小化する。GitLabの遅延削除機能（adjourned deletion）と連携し、猶予期間中にグループを保持（retain）できることを通知する。

**通知の送信タイミング**：グループが削除予定としてマークされた時点で即座に送信される。具体的には `Groups::MarkForDeletionService` が実行され、削除スケジュールが保存された後に `NotificationService#group_scheduled_for_deletion` が呼び出されるタイミングである。

**通知の受信者**：対象グループのアクティブなオーナー（Owner権限を持つメンバー）全員に送信される。招待中・リクエスト中のメンバーは除外される。

**通知内容の概要**：グループ名、削除までの日数、削除予定日、および削除をキャンセルするためのリンクが含まれる。

**期待されるアクション**：受信者は通知内容を確認し、削除が意図したものであればそのまま放置し、誤操作や意図しない削除の場合は猶予期間内にグループ設定画面から「retain」操作を行い削除をキャンセルする。

## 通知種別

メール（HTML形式およびテキスト形式）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 中 |
| リトライ | Sidekiqのデフォルトリトライ設定に従う |

### 送信先決定ロジック

1. 対象グループのメンバーを取得
2. `active_without_invites_and_requests` スコープでアクティブなメンバーのみを抽出
3. `owners` スコープでOwner権限（access_level = 50）を持つメンバーのみを抽出
4. 各オーナーの `user` オブジェクトを取得し、受信者リストを作成
5. グループの `emails_disabled?` が true の場合は送信しない

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | Gitlab.config.gitlab.email_from |
| 送信元名称 | Gitlab.config.gitlab.email_display_name |
| 件名 | `[グループ名] | Group scheduled for deletion` |
| 形式 | HTML/テキスト両対応（マルチパート） |

### 本文テンプレート（テキスト形式）

```
Hi {username}!
Your group {group_name} has been marked for deletion and will be removed in {days} days.
View your group: {group_url}
If this was a mistake, you can retain the group before {deletion_date}: {retention_url}
```

### 本文テンプレート（HTML形式）

```html
<p>Hi {username}!</p>
<p>Your group <a href="{group_url}">{group_name}</a> has been marked for deletion and will be removed in {days} days.</p>
<p>If this was a mistake, you can <a href="{retention_url}">retain the group</a> before {deletion_date}.</p>
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 添付ファイルなし |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| username | 受信者のユーザー名 | User#name | Yes |
| group_name | グループのフルパス名 | Group#full_name | Yes |
| days | 削除までの日数 | Gitlab::CurrentSettings.deletion_adjourned_period | Yes |
| group_url | グループのURL | group_url(@group) | Yes |
| deletion_date | 削除予定日（フォーマット: %B %-d, %Y） | permanent_deletion_date_formatted | Yes |
| retention_url | グループ保持用URL | groups/edit#{js-advanced-settings} | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | グループ削除実行 | 遅延削除が有効かつ権限あり | グループ設定画面から削除を実行した場合 |
| API | Groups::MarkForDeletionService実行 | 遅延削除が有効かつ権限あり | APIまたはサービス経由での削除マーク |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| group.emails_disabled? が true | グループのメール通知が無効化されている場合 |
| オーナーが存在しない | アクティブなオーナーがいない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Groups::MarkForDeletionService#execute] --> B{権限チェック}
    B -->|NG| C[エラー終了]
    B -->|OK| D[削除スケジュール保存]
    D --> E[NotificationService#group_scheduled_for_deletion]
    E --> F{emails_disabled?}
    F -->|Yes| G[送信スキップ]
    F -->|No| H[オーナー一覧取得]
    H --> I[各オーナーにメール送信]
    I --> J[Notify#group_scheduled_for_deletion]
    J --> K[deliver_later]
    K --> L[Sidekiqジョブキュー]
    L --> M[メール送信完了]
```

## データベース参照・更新仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| groups | グループ情報取得 | full_name, full_path等 |
| members | グループメンバー取得 | オーナー抽出用 |
| users | ユーザー情報取得 | メール送信先、name取得 |
| application_settings | 遅延削除期間取得 | deletion_adjourned_period |
| namespace_settings | メール無効化フラグ | emails_disabled確認 |

### テーブル別参照項目詳細

#### groups

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | グループ識別 | 引数で指定 |
| name | グループ名 | - |
| path | グループパス | - |

#### members

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| source_id | グループID | source_id = group.id |
| source_type | ソースタイプ | source_type = 'Namespace' |
| access_level | アクセスレベル | access_level = 50 (OWNER) |
| user_id | ユーザーID | NOT NULL |

#### users

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | ユーザー識別 | members.user_id |
| name | 表示名 | - |
| email | メールアドレス | 送信先 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | 本通知では更新処理なし |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| グループ未検出 | Group.find(group_id)で該当なし | ActiveRecord::RecordNotFound例外発生、ジョブ失敗 |
| ユーザー未検出 | User.find(recipient_id)で該当なし | ActiveRecord::RecordNotFound例外発生、ジョブ失敗 |
| SMTP接続エラー | メールサーバー接続失敗 | Sidekiqリトライ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | SMTPConnectionError等のネットワークエラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | Sidekiqキューの設定に依存 |
| 1日あたり上限 | 特に制限なし |

### 配信時間帯

特に制限なし（削除マーク時に即座に送信）

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

- 受信者はグループのオーナーのみに限定されており、権限のないユーザーには送信されない
- メール本文にはグループ名とURLが含まれるが、機密情報は含まれない
- 保持用リンクはグループ設定画面へのリンクであり、実際の操作には認証が必要

## 備考

- 遅延削除期間は `Gitlab::CurrentSettings.deletion_adjourned_period` で設定され、デフォルトは7日間
- グループがコンテナレジストリを持つ場合、名前の変更は行われない
- EE版では追加の通知対象や機能が存在する可能性がある

---

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

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

### 推奨読解順序

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

まず、通知に関連するモデルの構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | member.rb | `app/models/member.rb` | Memberモデルの構造、特にスコープ（owners, active_without_invites_and_requests）を確認 |
| 1-2 | group.rb | `app/models/group.rb` | Groupモデルの関連、emails_disabled?メソッドを確認 |

**読解のコツ**: Railsのスコープ（scope）は、where句やjoins句を組み合わせたクエリビルダーであり、チェーンして使用できる。

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

処理の起点となるサービスクラスを特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | mark_for_deletion_service.rb | `app/services/groups/mark_for_deletion_service.rb` | グループ削除マークの起点、notification_methodの定義を確認 |
| 2-2 | mark_for_deletion_base_service.rb | `app/services/namespaces/mark_for_deletion_base_service.rb` | send_notificationメソッドでNotificationServiceを呼び出す流れを確認 |

**主要処理フロー**:
1. **14-28行目**: execute メソッドで前提条件チェック、削除実行、通知送信を順次実行
2. **76-77行目**: send_notification で NotificationService の該当メソッドを呼び出し

#### Step 3: 通知サービスを理解する

NotificationServiceでの受信者決定ロジックを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_service.rb | `app/services/notification_service.rb` | group_scheduled_for_deletionメソッド（790-801行目）を確認 |

**主要処理フロー**:
- **790行目**: メソッド定義
- **791行目**: emails_disabled?チェック
- **793行目**: オーナーの抽出ロジック
- **795-800行目**: 各オーナーへのメール送信ループ

#### Step 4: メーラーを理解する

実際のメール送信処理を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | groups.rb | `app/mailers/emails/groups.rb` | group_scheduled_for_deletion メソッド（21-31行目）を確認 |
| 4-2 | notify.rb | `app/mailers/notify.rb` | Notifyクラスがどのモジュールをincludeしているか確認 |

**主要処理フロー**:
- **21-31行目**: メーラーメソッドの実装、インスタンス変数の設定、email_with_layoutの呼び出し

#### Step 5: テンプレートを理解する

メール本文のテンプレートを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | group_scheduled_for_deletion.html.haml | `app/views/notify/group_scheduled_for_deletion.html.haml` | HTML形式のテンプレート |
| 5-2 | group_scheduled_for_deletion.text.erb | `app/views/notify/group_scheduled_for_deletion.text.erb` | テキスト形式のテンプレート |

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

```
Groups::MarkForDeletionService#execute
    │
    ├─ preconditions_checks
    │      └─ can?(current_user, :remove_group, resource)
    │
    ├─ execute_deletion
    │      └─ 削除スケジュール保存処理
    │
    └─ send_notification (MarkForDeletionBaseService)
           │
           └─ NotificationService#group_scheduled_for_deletion
                  │
                  ├─ group.emails_disabled?
                  │
                  ├─ group.members.active_without_invites_and_requests.owners
                  │
                  └─ Notify#group_scheduled_for_deletion (各オーナーごと)
                         │
                         ├─ Group.find(group_id)
                         ├─ User.find(recipient_id)
                         └─ email_with_layout → deliver_later
```

### データフロー図

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

Group ID        ───▶ MarkForDeletionService     ───▶ 削除スケジュール作成
Current User           │
                       ▼
                  NotificationService
                       │
                       ├─▶ Group.members        ───▶ オーナー一覧
                       │
                       └─▶ Notify Mailer        ───▶ メール送信（Sidekiq）
                              │
                              ├─▶ @group (Group)
                              ├─▶ @user (User)
                              ├─▶ @deletion_due_in_days
                              └─▶ @deletion_date
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| mark_for_deletion_service.rb | `app/services/groups/mark_for_deletion_service.rb` | ソース | グループ削除マークサービス |
| mark_for_deletion_base_service.rb | `app/services/namespaces/mark_for_deletion_base_service.rb` | ソース | 削除マーク基底サービス |
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知サービス（受信者決定） |
| groups.rb | `app/mailers/emails/groups.rb` | ソース | グループ関連メーラーモジュール |
| notify.rb | `app/mailers/notify.rb` | ソース | メーラークラス本体 |
| application_mailer.rb | `app/mailers/application_mailer.rb` | ソース | メーラー基底クラス |
| group_scheduled_for_deletion.html.haml | `app/views/notify/group_scheduled_for_deletion.html.haml` | テンプレート | HTML形式メールテンプレート |
| group_scheduled_for_deletion.text.erb | `app/views/notify/group_scheduled_for_deletion.text.erb` | テンプレート | テキスト形式メールテンプレート |
| member.rb | `app/models/member.rb` | ソース | Memberモデル（スコープ定義） |
| deletable_helper.rb | `app/helpers/namespaces/deletable_helper.rb` | ソース | 削除日計算ヘルパー |
