# 通知設計書 51-access_token_about_to_expire_email

## 概要

本ドキュメントは、GitLabにおける個人アクセストークン期限切れ事前通知（access_token_about_to_expire_email）の設計内容を記載する。

### 本通知の処理概要

**業務上の目的・背景**：個人アクセストークン（PAT）は、APIアクセスやGit操作の認証に使用される重要なセキュリティ資格情報である。トークンが予期せず期限切れになると、CI/CDパイプライン、自動化スクリプト、サードパーティ連携などが突然動作を停止し、業務に重大な影響を及ぼす可能性がある。本通知は、トークン期限切れ前にユーザーに警告を発し、事前に更新作業を行う機会を提供することで、サービス中断を防止する。

**通知の送信タイミング**：定期的なcronジョブ（PersonalAccessTokens::ExpiringWorker）により、有効期限が7日以内に迫っているトークンを検出し、通知を送信する。複数の通知間隔（7日、30日など）が設定可能で、各間隔ごとに1回のみ通知される。

**通知の受信者**：トークンを所有するユーザー本人。ユーザーが通知を受信できる権限（receive_notifications）を持っている必要がある。

**通知内容の概要**：期限切れが近いトークンの名前一覧、残り日数、個人アクセストークン設定画面へのリンクが含まれる。

**期待されるアクション**：受信者は通知を受け取った後、個人アクセストークン設定画面にアクセスし、既存トークンの期限を延長するか、新しいトークンを作成して既存のトークンを置き換えることが期待される。

## 通知種別

メール

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（deliver_later） |
| 優先度 | 中 |
| リトライ | Sidekiq標準リトライ機構に依存 |

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

トークンを所有するユーザーの`notification_email_or_default`メソッドで取得されるメールアドレスに送信される。ユーザーが`can?(:receive_notifications)`を満たす場合のみ送信が実行される。

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | GitLab設定に基づく |
| 送信元名称 | GitLab |
| 件名 | "Your personal access tokens will expire in {days_to_expire} days or less" |
| 形式 | テキスト |

### 本文テンプレート

```
Hi {username}!

One or more of your personal access tokens will expire in {days_to_expire} days or less:

  - {token_name_1}
  - {token_name_2}
  ...

You can create a new one or check them in your personal access tokens settings {pat_link}.
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| @user | 通知対象ユーザー | User | Yes |
| @token_names | 期限切れが近いトークン名の配列 | PersonalAccessToken.name | Yes |
| @days_to_expire | 期限切れまでの日数 | パラメータ（デフォルト7日） | Yes |
| @target_url | 個人アクセストークン設定URL | user_settings_personal_access_tokens_url | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| スケジュール（Cron） | PersonalAccessTokens::ExpiringWorker | トークンの有効期限が設定された日数以内 | 毎日実行されるワーカーで期限切れが近いトークンを検出 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| ユーザーが通知不可 | user.can?(:receive_notifications)がfalseの場合 |
| 既に通知済み | 該当interval_notification_sent_atが設定済みの場合 |
| ユーザーが存在しない | userがnilの場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[Cronジョブ実行] --> B[ExpiringWorker.perform]
    B --> C[通知間隔ループ]
    C --> D[期限切れ近いトークン取得]
    D --> E{トークン存在?}
    E -->|No| F[次の間隔へ]
    E -->|Yes| G[ユーザー取得]
    G --> H{通知可能?}
    H -->|No| F
    H -->|Yes| I[notification_service.access_token_about_to_expire]
    I --> J[Emails::Profile.access_token_about_to_expire_email]
    J --> K[deliver_later]
    K --> L[通知済みフラグ更新]
    L --> F
    F --> M{全間隔完了?}
    M -->|No| C
    M -->|Yes| N[終了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| users | ユーザー情報取得 | |
| personal_access_tokens | トークン情報取得 | |

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

#### personal_access_tokens

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| name | トークン名 | 通知メール本文に記載 |
| expires_at | 有効期限 | 期限切れ判定に使用 |
| user_id | 所有者ID | ユーザー特定 |
| seven_days_notification_sent_at | 7日前通知送信日時 | 送信済み判定 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| personal_access_tokens | UPDATE | 通知送信済みフラグを更新 |

#### personal_access_tokens更新

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| UPDATE | seven_days_notification_sent_at | Time.current | 7日前通知の場合 |
| UPDATE | expire_notification_delivered | true | 後方互換性のため |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| 送信失敗 | メール送信エラー | Sidekiqリトライ |
| ユーザー不存在 | トークン所有者が削除済み | 処理スキップ |
| リソース不足 | ワーカータイムアウト | 2分後に再キュー |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Sidekiq標準（25回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | 一時的なエラー全般 |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | GitLab設定に依存 |
| 1日あたり上限 | 特になし |

### 配信時間帯

Cronジョブの実行スケジュールに依存。通常は毎日実行。

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

- トークン名のみを通知に含め、トークン値自体は含まれない
- 通知はトークン所有者本人にのみ送信される
- メールアドレスはユーザーの設定に基づいて決定される

## 備考

- 最大100件のトークン名がメールに含まれる（MAX_TOKENS = 100）
- ワーカーの最大実行時間は3分（MAX_RUNTIME）
- タイムアウト時は2分後に再キュー（REQUEUE_DELAY）

---

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

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

### 推奨読解順序

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

まず、通知に関わるデータモデルを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | personal_access_token.rb | `app/models/personal_access_token.rb` | トークンモデルの構造、expires_at、通知関連カラムを確認 |
| 1-2 | user.rb | `app/models/user.rb` | notification_email_or_default、can?メソッドを確認 |

**読解のコツ**: PersonalAccessTokenモデルのNOTIFICATION_INTERVALSとscope_for_notification_intervalメソッドに注目。

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

ワーカーが処理の起点となる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | expiring_worker.rb | `app/workers/personal_access_tokens/expiring_worker.rb` | performメソッドから処理フローを追う |

**主要処理フロー**:
1. **27行目**: perform メソッドでnotification_intervalsをループ
2. **50-96行目**: process_user_tokens でユーザートークンを処理
3. **81行目**: deliver_user_notifications で通知サービスを呼び出し

#### Step 3: NotificationServiceを理解する

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

**主要処理フロー**:
- **126行目**: 通知可能かチェック
- **131行目**: mailer.access_token_about_to_expire_email呼び出し

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | profile.rb | `app/mailers/emails/profile.rb` | access_token_about_to_expire_emailメソッド（105-123行目） |

**主要処理フロー**:
- **106行目**: ユーザー存在チェック
- **111行目**: @token_namesにトークン名を設定
- **115-122行目**: email_with_layoutでメール生成

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | access_token_about_to_expire_email.text.erb | `app/views/notify/access_token_about_to_expire_email.text.erb` | メール本文テンプレート |

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

```
PersonalAccessTokens::ExpiringWorker#perform
    │
    ├─ notification_intervals (7日、30日など)
    │      └─ process_user_tokens(interval)
    │             ├─ PersonalAccessToken.scope_for_notification_interval
    │             ├─ User.id_in
    │             └─ deliver_user_notifications
    │                    └─ NotificationService#access_token_about_to_expire
    │                           └─ Notify#access_token_about_to_expire_email
    │                                  └─ email_with_layout
    │
    └─ process_project_bot_tokens(interval)
           └─ deliver_bot_notifications（Bot用は別処理）
```

### データフロー図

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

personal_access_tokens  ──▶ ExpiringWorker            ──▶ メール送信
  (expires_at,              ├─ 期限チェック                 │
   notification_sent_at)    ├─ ユーザー取得                 ▼
                            └─ NotificationService    ──▶ Sidekiq Job
users                   ──▶     └─ Emails::Profile        (deliver_later)
  (notification_email)          └─ テンプレート展開
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| expiring_worker.rb | `app/workers/personal_access_tokens/expiring_worker.rb` | ソース | Cronワーカー |
| notification_service.rb | `app/services/notification_service.rb` | ソース | 通知サービス |
| profile.rb | `app/mailers/emails/profile.rb` | ソース | メーラー |
| access_token_about_to_expire_email.text.erb | `app/views/notify/access_token_about_to_expire_email.text.erb` | テンプレート | メール本文 |
| personal_access_token.rb | `app/models/personal_access_token.rb` | ソース | トークンモデル |
| notify_preview.rb | `app/mailers/previews/notify_preview.rb` | ソース | メールプレビュー |
