# 通知設計書 1-イベント通知（カード公開）

## 概要

本ドキュメントは、Fizzyアプリケーションにおける「カード公開」イベント発生時のアプリ内通知機能について、その設計仕様を記載するものである。

### 本通知の処理概要

カードが公開（card_published）された際に、該当ボードのウォッチャーに対してアプリ内通知を送信する機能を提供する。

**業務上の目的・背景**：チーム開発やプロジェクト管理において、新しいカード（タスク・課題・アイデア等）がボードに追加されたことをチームメンバーに即座に共有する必要がある。これにより、情報の見落としを防ぎ、チーム全体の状況把握とコラボレーションを促進する。特に、複数人が同じボードで作業する環境では、新規カードの追加を見逃すと、作業の重複や対応漏れが発生するリスクがある。

**通知の送信タイミング**：カードのステータスが「drafted（下書き）」から「published（公開）」に変更された際に、Eventモデルが作成され、そのafter_create_commitコールバックによりNotifyRecipientsJobが非同期で実行される。

**通知の受信者**：対象ボードのウォッチャーのうち、カード作成者（creator）およびカード本文中でメンションされたユーザー（mentionees）を除外した全員が受信者となる。ただし、カードの担当者（assignees）は明示的に含まれる。

**通知内容の概要**：通知には、カードのタイトル（またはカード番号）、追加者の名前、対象ボード情報が含まれる。ユーザーは通知から直接カード詳細画面へ遷移できる。

**期待されるアクション**：受信者は通知を確認し、必要に応じてカードの内容を確認、コメントの追加、担当者としてのアサイン受諾などのアクションを行うことが期待される。

## 通知種別

アプリ内通知

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（Active Job） |
| 優先度 | 中 |
| リトライ | Active Jobのデフォルト設定に準拠 |

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

1. カードが所属するボード（`card.board`）のウォッチャー一覧を取得
2. カード作成者（`event.creator`）を除外
3. カード本文中でメンションされたユーザー（`card.mentionees`）を除外
4. カードの担当者（`card.assignees`）を明示的に含める
5. 重複を排除（`uniq`）

```ruby
board.watchers.without(creator, *card.mentionees).including(*card.assignees).uniq
```

## 通知テンプレート

### アプリ内通知

| 項目 | 内容 |
|-----|------|
| 表示形式 | リスト形式（通知一覧画面） |
| アバター | 作成者のアバター画像 |
| リンク先 | カード詳細画面 |

### 本文テンプレート

Webプッシュ通知連動時のメッセージ:
```
Title: {カードタイトル} または "Card {カード番号}"
Body: "Added by {作成者名}"
```

### 添付ファイル

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

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| card.title | カードのタイトル | cards.title | No |
| card.number | カード番号 | cards.number | Yes |
| creator.name | 作成者名 | users.name | Yes |
| board.name | ボード名 | boards.name | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | カード公開ボタン押下 | action = "card_published" | カードが下書きから公開状態に変更された際 |
| API | カード作成API | action = "card_published" | APIを通じてカードが公開された際 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| creator.system? = true | システムユーザーによる操作の場合は通知しない |
| 受信者が0人 | ウォッチャーが存在しない、または全員が除外対象の場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[カード公開イベント発生] --> B[Event作成]
    B --> C[after_create_commit]
    C --> D[NotifyRecipientsJob キュー投入]
    D --> E[Notifier.for source 実行]
    E --> F[Notifier::CardEventNotifier 生成]
    F --> G[should_notify? チェック]
    G -->|creator.system? = false| H[recipients 決定]
    G -->|creator.system? = true| I[処理終了]
    H --> J[各recipientにNotification作成/更新]
    J --> K[after_create_commit: bundle処理]
    K --> L[after_save_commit: PushNotificationJob]
    L --> M[Turbo Stream配信]
    M --> N[終了]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| events | イベント情報取得 | source_typeがEventの場合 |
| cards | カード情報取得 | eventable経由 |
| boards | ボード情報取得 | card.board |
| users | ユーザー情報取得 | creator, watchers |
| watches | ウォッチャー情報取得 | board.watchersの取得 |
| mentions | メンション情報取得 | card.mentionees |
| assignments | 担当者情報取得 | card.assignees |

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

#### events

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | イベント識別子 | 通知のsource_id |
| action | アクション種別 | "card_published" |
| creator_id | 作成者ID | 通知除外判定 |
| eventable_id | 対象カードID | カード情報取得 |
| board_id | ボードID | ウォッチャー取得 |

#### notifications

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| user_id | 受信者ID | 既存通知確認 |
| card_id | カードID | 既存通知確認 |
| source_id | ソースID | 更新対象特定 |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| notifications | INSERT/UPDATE | 通知レコードの作成または更新 |
| notification_bundles | INSERT/UPDATE | メールバンドル用レコード |

#### notificationsテーブル

| 操作 | 項目（カラム名） | 更新値 | 備考 |
|-----|-----------------|-------|------|
| INSERT/UPDATE | user_id | 受信者のID | 通知対象ユーザー |
| INSERT/UPDATE | card_id | 対象カードのID | set_cardで自動設定 |
| INSERT/UPDATE | source_type | "Event" | ポリモーフィック関連 |
| INSERT/UPDATE | source_id | EventのID | イベント参照 |
| INSERT/UPDATE | creator_id | 作成者のID | 通知作成者 |
| INSERT/UPDATE | unread_count | 1（新規）/ +1（更新） | 未読カウント |
| UPDATE | read_at | nil | 再度未読にする |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| ActiveJob::DeserializationError | 通知対象レコードが削除済み | PushNotificationJobでdiscard |
| ActiveRecord::RecordNotUnique | 同一user_id, card_idの重複 | create_or_find_byで対応済み |
| ActiveRecord::RecordInvalid | バリデーションエラー | 例外発生、ジョブリトライ |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | Active Jobデフォルト（5回） |
| リトライ間隔 | 指数バックオフ |
| リトライ対象エラー | StandardError（DeserializationError除く） |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし（24時間配信可能）

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

- 通知はaccount_idでスコープされ、同一アカウント内のユーザーにのみ配信される
- Turbo Stream配信はユーザー固有のチャンネル（`user, :notifications`）を使用
- 通知内容にはカードタイトルが含まれるが、アクセス権のないユーザーには配信されない設計

## 備考

- カード公開時にメンションされたユーザーには、別途MentionNotifierによるメンション通知が送信される
- 同一カードに対する複数の通知は、最新のsourceで上書きされ、unread_countがインクリメントされる
- Webプッシュ通知はPushNotifiableコンサーンにより自動的に連動する

---

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

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

### 推奨読解順序

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

まず、通知処理で使用されるデータモデルの構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification.rb | `app/models/notification.rb` | 通知モデルの構造、スコープ、コールバック定義（1-69行） |
| 1-2 | event.rb | `app/models/event.rb` | イベントモデル、Notifiableインクルード（1-44行） |
| 1-3 | schema.rb | `db/schema.rb` | notifications, eventsテーブル定義（281-296, 387-404行） |

**読解のコツ**: Railsのポリモーフィック関連（`belongs_to :source, polymorphic: true`）により、通知のソースはEventまたはMentionになる。source_typeカラムで判別される。

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notifiable.rb | `app/models/concerns/notifiable.rb` | after_create_commitでnotify_recipients_laterが呼ばれる（7行目） |
| 2-2 | notifier.rb | `app/models/notifier.rb` | Notifier.forでソースに応じたNotifierを生成（4-13行目） |

**主要処理フロー**:
1. **7行目**: `after_create_commit :notify_recipients_later` - Event作成後に非同期処理開始
2. **19-21行目**: `NotifyRecipientsJob.perform_later self` - ジョブをキューに投入

#### Step 3: 通知生成ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notifier.rb | `app/models/notifier.rb` | notifyメソッドの実装（15-38行目） |
| 3-2 | card_event_notifier.rb | `app/models/notifier/card_event_notifier.rb` | card_publishedの受信者決定ロジック（6-16行目） |

**主要処理フロー**:
- **8-11行目（card_event_notifier.rb）**: card_publishedアクションの受信者決定
  ```ruby
  board.watchers.without(creator, *card.mentionees).including(*card.assignees).uniq
  ```
- **19-23行目（notifier.rb）**: Notification.create_or_find_byで通知作成/更新

#### Step 4: 通知配信を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | push_notifiable.rb | `app/models/concerns/push_notifiable.rb` | プッシュ通知の自動トリガー（5行目） |
| 4-2 | notification_pusher.rb | `app/models/notification_pusher.rb` | プッシュ通知ペイロード構築（38-75行目） |
| 4-3 | push_notification_job.rb | `app/jobs/push_notification_job.rb` | プッシュ通知ジョブ実行（4-6行目） |

**主要処理フロー**:
- **5行目（push_notifiable.rb）**: `after_save_commit :push_notification_later, if: :source_id_previously_changed?`
- **58-62行目（notification_pusher.rb）**: card_publishedのペイロード生成

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

```
Event#create (カード公開時)
    │
    ├─ Notifiable#notify_recipients_later (after_create_commit)
    │      └─ NotifyRecipientsJob.perform_later(self)
    │             └─ Notifier.for(event)
    │                    └─ Notifier::CardEventNotifier.new(event)
    │                           └─ #notify
    │                                  ├─ #recipients (受信者決定)
    │                                  └─ Notification.create_or_find_by
    │                                         ├─ #bundle (after_create)
    │                                         │     └─ User#bundle(notification)
    │                                         └─ PushNotifiable#push_notification_later (after_save_commit)
    │                                               └─ PushNotificationJob.perform_later
    │                                                      └─ NotificationPusher#push
    └─ Turbo Stream broadcast (after_create_commit)
```

### データフロー図

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

Event                ───▶ Notifier.for(event)          ───▶ Notifier::CardEventNotifier
  │                                                           │
  ├─ action                                                   ├─ recipients決定
  ├─ creator_id                                               │   └─ board.watchers
  ├─ eventable (Card)                                         │       - creator除外
  └─ board_id                                                 │       - mentionees除外
                                                              │       + assignees追加
                                                              │
                                                              └─ Notification
                                                                    │
                                                                    ├─ notifications テーブル INSERT/UPDATE
                                                                    ├─ notification_bundles テーブル INSERT
                                                                    ├─ Turbo Stream broadcast
                                                                    └─ PushNotificationJob キュー投入
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification.rb | `app/models/notification.rb` | モデル | 通知モデル定義 |
| event.rb | `app/models/event.rb` | モデル | イベントモデル定義 |
| notifier.rb | `app/models/notifier.rb` | モデル | 通知生成の基底クラス |
| card_event_notifier.rb | `app/models/notifier/card_event_notifier.rb` | モデル | カードイベント用通知生成 |
| notifiable.rb | `app/models/concerns/notifiable.rb` | Concern | 通知可能モデル用Concern |
| push_notifiable.rb | `app/models/concerns/push_notifiable.rb` | Concern | プッシュ通知用Concern |
| notification_pusher.rb | `app/models/notification_pusher.rb` | モデル | プッシュ通知送信処理 |
| push_notification_job.rb | `app/jobs/push_notification_job.rb` | ジョブ | プッシュ通知非同期ジョブ |
| user/notifiable.rb | `app/models/user/notifiable.rb` | Concern | ユーザーの通知関連処理 |
| notifications_controller.rb | `app/controllers/notifications_controller.rb` | コントローラ | 通知一覧表示 |
| _notification.html.erb | `app/views/notifications/_notification.html.erb` | ビュー | 通知表示テンプレート |
| schema.rb | `db/schema.rb` | スキーマ | テーブル定義 |
