# 通知設計書 49-bot_resource_access_token_about_to_expire_email

## 概要

本ドキュメントは、GitLabにおけるBotリソースアクセストークン有効期限通知（bot_resource_access_token_about_to_expire_email）の設計仕様を定義するものである。

### 本通知の処理概要

プロジェクトまたはグループに紐づくBotユーザーのリソースアクセストークンが有効期限切れに近づいた際に、リソースのOwnerおよびMaintainerに通知を送信する機能である。

**業務上の目的・背景**：リソースアクセストークン（プロジェクトアクセストークン、グループアクセストークン）は、CI/CDパイプラインやAPI連携で広く使用される。これらのトークンが期限切れになると、自動化されたワークフローが中断する可能性がある。事前に通知することで、管理者は新しいトークンを準備し、サービスの中断を防ぐことができる。

**通知の送信タイミング**：トークンの有効期限がデフォルトで7日以内（PersonalAccessToken::DAYS_TO_EXPIRE）に迫った時点で、定期的なバッチ処理によりトリガーされる。

**通知の受信者**：リソース（プロジェクトまたはグループ）のOwnerおよびMaintainerロールを持つメンバー。継承設定が有効な場合は、親グループのOwner/Maintainerも含まれる。

**通知内容の概要**：トークン名、対象リソース名、有効期限までの日数、トークン管理画面へのリンクが含まれる。

**期待されるアクション**：受信者は新しいトークンを作成するか、既存のトークンをローテーションする。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 高（運用に影響） |
| リトライ | Sidekiqのデフォルトリトライ機能に依存 |

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

1. Botユーザーからリソース（プロジェクトまたはグループ）を取得
2. `bot_resource_access_token_about_to_expire_recipients`メソッドで受信者を決定
3. 継承設定（resource_access_token_notify_inherited?）に応じて:
   - 有効: 親グループを含むOwner/Maintainerを取得
   - 無効: 直接のOwner/Maintainerのみ取得
4. 各受信者に対して通知を送信

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLabインスタンスのデフォルト送信アドレス |
| 送信元名称 | GitLab |
| 件名 | `Your resource access tokens will expire in {日数} or less` |
| 形式 | HTML/テキスト（マルチパート） |

### 本文テンプレート

```
Hi {ユーザー名}!

Your {リソースタイプ} access token {トークン名} for {リソースパス} will expire in {日数} or less.

You can create a new one or check them in your access tokens settings:
{アクセストークン設定URL}

{理由テキスト}
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @user | 受信者情報 | 引数recipient | Yes |
| @token_name | トークン名 | 引数token_name | Yes |
| @days_to_expire | 有効期限までの日数 | params[:days_to_expire]またはデフォルト | Yes |
| @resource | リソース情報（プロジェクトまたはグループ） | bot_user.resource_bot_resource | Yes |
| @target_url | アクセストークン設定URL | project/group_settings_access_tokens_url | Yes |
| @reason_text | 通知を受け取る理由 | リソースタイプに応じて設定 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| バッチ処理 | トークン有効期限チェック | 有効期限が指定日数以内 | 定期的なバッチ処理で実行 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 通知受信不可 | recipient.can?(:receive_notifications)がfalseの場合はスキップ |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[バッチ処理開始] --> B[期限切れ近いトークン検出]
    B --> C[bot_resource_access_token_about_to_expire呼び出し]
    C --> D[リソース取得]
    D --> E{継承設定有効?}
    E -->|Yes| F[親グループ含むOwner/Maintainer取得]
    E -->|No| G[直接のOwner/Maintainer取得]
    F --> H[受信者ループ]
    G --> H
    H --> I{通知受信可能?}
    I -->|No| J[スキップ]
    I -->|Yes| K[bot_resource_access_token_about_to_expire_email呼び出し]
    K --> L[email_with_layout実行]
    L --> M[deliver_later]
    M --> N[次の受信者へ]
    J --> N
    N --> O{全受信者処理完了?}
    O -->|No| H
    O -->|Yes| P[終了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| personal_access_tokens | トークン情報取得 | |
| users | Botユーザー情報取得 | |
| projects | プロジェクト情報取得 | リソースがプロジェクトの場合 |
| namespaces | グループ情報取得 | リソースがグループの場合 |
| members | Owner/Maintainer取得 | |

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

#### personal_access_tokens

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| name | トークン名 | |
| expires_at | 有効期限 | DAYS_TO_EXPIRE以内 |
| user_id | Botユーザー参照 | |

#### members

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| user_id | ユーザーID | access_level >= OWNER or MAINTAINER |
| access_level | アクセスレベル | |
| source_type | ソースタイプ | Project or Group |

### 更新テーブル一覧

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

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | メールサーバー接続エラー | Sidekiqリトライ |
| リソース未検出 | bot_userに紐づくリソースがない | ログ記録、スキップ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiqデフォルト（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | ネットワークエラー、一時的なサーバーエラー |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | GitLabインスタンス設定による |
| 1日あたり上限 | GitLabインスタンス設定による |

### 配信時間帯

特に制限なし（バッチ処理の実行時間に依存）

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

- トークン値自体はメールに含まれない
- 受信者はリソースのOwner/Maintainerに限定
- アクセストークン設定リンクは認証後のみアクセス可能

## 備考

- PersonalAccessToken::DAYS_TO_EXPIREはデフォルト7日
- 継承設定はnamespace.resource_access_token_notify_inherited?で判定
- プロジェクトとグループで異なる@reason_textが設定される

---

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

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

### 推奨読解順序

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

まず、PersonalAccessTokenとBotユーザーの関係を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | personal_access_token.rb | `app/models/personal_access_token.rb` | DAYS_TO_EXPIRE定数、スコープ定義を確認 |

**読解のコツ**: `expiring_and_not_notified`スコープに注目し、通知対象トークンの抽出条件を理解する。

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

処理の起点となるNotificationServiceを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_service.rb | `app/services/notification_service.rb` | bot_resource_access_token_about_to_expireメソッド（100-115行目）がエントリーポイント |

**主要処理フロー**:
1. **100行目**: メソッド定義、bot_user、token_name、paramsを引数として受け取る
2. **101行目**: bot_user.resource_bot_resourceでリソース取得
3. **103-114行目**: 受信者ループで各メンバーにメール送信

#### Step 3: 受信者決定ロジックを理解する

受信者の決定ロジックを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_service.rb | `app/services/notification_service.rb` | bot_resource_access_token_about_to_expire_recipientsメソッド（982-994行目） |

**主要処理フロー**:
- **985-993行目**: 継承設定に応じてinherited_rat_members_relationまたはresource_bot_owners_and_maintainersを使用

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

メール生成処理の詳細を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | profile.rb | `app/mailers/emails/profile.rb` | bot_resource_access_token_about_to_expire_emailメソッド（67-90行目）の実装 |

**主要処理フロー**:
- **67行目**: メソッド定義、recipient、resource、token_name、paramsを引数として受け取る
- **73-78行目**: リソースタイプに応じてURL・理由テキストを設定
- **81-89行目**: email_with_layoutでメール生成

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

メール本文の生成を確認。

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

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

```
NotificationService#bot_resource_access_token_about_to_expire (100-115行目)
    │
    ├─ bot_user.resource_bot_resource (101行目)
    │      └─ リソース（Project/Group）取得
    │
    ├─ bot_resource_access_token_about_to_expire_recipients (103行目)
    │      ├─ send_bot_rat_expiry_to_inherited? (985行目)
    │      │      └─ 継承設定確認
    │      ├─ inherited_rat_members_relation (986行目)
    │      │      └─ 親グループ含むメンバー取得
    │      └─ resource_bot_owners_and_maintainers (990行目)
    │             └─ 直接メンバー取得
    │
    └─ Notify#bot_resource_access_token_about_to_expire_email (108-113行目)
           └─ Emails::Profile#bot_resource_access_token_about_to_expire_email (67-90行目)
                  └─ email_with_layout
```

### データフロー図

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

トークン期限チェック      ───▶ NotificationService            ───▶ メール送信キュー
(bot_user, token_name)         │                                    (Sidekiq)
                               ├─ リソース取得
                               │   └─ resource_bot_resource
                               │
                               ├─ 受信者決定
                               │   └─ Owner/Maintainer取得
                               │
                               └─ bot_resource_access_token_about_to_expire_email
                                    │
                                    ├─ @user (受信者)
                                    ├─ @token_name (トークン名)
                                    ├─ @days_to_expire (残り日数)
                                    ├─ @resource (プロジェクト/グループ)
                                    ├─ @target_url (設定URL)
                                    └─ @reason_text (理由)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知処理のオーケストレーション |
| profile.rb | `app/mailers/emails/profile.rb` | ソース | プロファイル関連メール定義 |
| notify.rb | `app/mailers/notify.rb` | ソース | メーラー基底クラス |
| personal_access_token.rb | `app/models/personal_access_token.rb` | ソース | トークンモデル |
| bot_resource_access_token_about_to_expire_email.html.haml | `app/views/notify/bot_resource_access_token_about_to_expire_email.html.haml` | テンプレート | HTML版メールテンプレート |
| bot_resource_access_token_about_to_expire_email.text.erb | `app/views/notify/bot_resource_access_token_about_to_expire_email.text.erb` | テンプレート | テキスト版メールテンプレート |
