# 機能設計書 104-通知サブスクリプション

## 概要

本ドキュメントは、Chatwootにおける通知サブスクリプション管理機能について記述する。ユーザーのデバイス（ブラウザ、モバイルアプリ）をプッシュ通知の配信先として登録・解除する機能である。

### 本機能の処理概要

**業務上の目的・背景**：プッシュ通知を配信するためには、ユーザーのデバイス情報（エンドポイント、トークン等）を事前に登録しておく必要がある。本機能により、ユーザーは複数のデバイスでプッシュ通知を受け取ることができ、デバイスの追加・削除を柔軟に管理できる。

**機能の利用シーン**：
- ユーザーがブラウザでプッシュ通知を許可した時（Web Push登録）
- ユーザーがモバイルアプリにログインした時（FCM登録）
- ユーザーがログアウトした時（サブスクリプション削除）
- 複数アカウントでログインしている場合のサブスクリプション移動

**主要な処理内容**：
1. サブスクリプションの新規登録
2. 既存サブスクリプションの更新
3. サブスクリプションの削除
4. 異なるユーザー間でのサブスクリプション移動（同一デバイスで別アカウントログイン時）

**関連システム・外部連携**：
- Web Push API: ブラウザ用サブスクリプション
- Firebase Cloud Messaging: モバイルアプリ用サブスクリプション

**権限による制御**：ログインユーザーは自分自身のサブスクリプションのみ登録・削除可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | - | - | API経由での登録/削除（画面表示なし） |

## 機能種別

CRUD操作（作成・削除）

## 入力仕様

### 入力パラメータ

#### 登録（create）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| subscription_type | String | Yes | サブスクリプションタイプ（browser_push / fcm） | enum値のみ許可 |
| subscription_attributes | Object | Yes | サブスクリプション属性（エンドポイント、トークン等） | - |
| subscription_attributes.endpoint | String | browser_push時必須 | Web Push エンドポイント | - |
| subscription_attributes.p256dh | String | browser_push時必須 | 公開鍵 | - |
| subscription_attributes.auth | String | browser_push時必須 | 認証シークレット | - |
| subscription_attributes.push_token | String | fcm時必須 | FCMプッシュトークン | - |
| subscription_attributes.device_id | String | fcm時必須 | デバイス識別子 | - |

#### 削除（destroy）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| push_token | String | Yes | 削除対象のプッシュトークン | - |

### 入力データソース

- 画面入力: ブラウザのPush API、モバイルアプリのFCM SDK
- DBテーブル: notification_subscriptions テーブルから既存サブスクリプションを検索

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Integer | サブスクリプションID |
| identifier | String | 一意識別子（endpoint または device_id） |
| subscription_type | String | サブスクリプションタイプ |
| subscription_attributes | Object | サブスクリプション属性 |
| user_id | Integer | ユーザーID |
| created_at | DateTime | 作成日時 |
| updated_at | DateTime | 更新日時 |

### 出力先

- 画面表示: JSON形式でAPIレスポンスとして返却

## 処理フロー

### 処理シーケンス

```
1. リクエスト受信
   └─ APIエンドポイント: /api/v1/notification_subscriptions

2. 認証チェック
   └─ current_user の確認

3. サブスクリプション処理（create）
   ├─ 識別子（endpoint/device_id）から既存サブスクリプションを検索
   ├─ 既存サブスクリプションが他ユーザーに属する場合は移動
   ├─ 既存サブスクリプションがない場合は新規作成
   └─ 既存サブスクリプションがある場合は更新

4. サブスクリプション削除（destroy）
   ├─ push_token からサブスクリプションを検索
   └─ 見つかった場合は削除
```

### フローチャート

```mermaid
flowchart TD
    A[リクエスト受信] --> B{アクション種別}
    B -->|create| C[識別子でサブスクリプション検索]
    B -->|destroy| D[push_tokenで検索]

    C --> E{既存あり?}
    E -->|No| F[新規作成]
    E -->|Yes| G{同一ユーザー?}
    G -->|No| H[ユーザーIDを更新]
    G -->|Yes| I[属性を更新]

    H --> I
    F --> J[JSON レスポンス]
    I --> J

    D --> K{見つかった?}
    K -->|Yes| L[削除]
    K -->|No| M[OK レスポンス]
    L --> M
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-104-01 | 識別子の一意性 | identifier は notification_subscriptions テーブルで一意 | 常時 |
| BR-104-02 | サブスクリプション移動 | 同一デバイスで別ユーザーがログインした場合、サブスクリプションを移動 | 識別子が既存で別ユーザーの場合 |
| BR-104-03 | 識別子の決定 | browser_push は endpoint、fcm は device_id を識別子とする | タイプに依存 |

### 計算ロジック

識別子の決定:
```ruby
identifier = params[:subscription_attributes][:endpoint] if params[:subscription_type] == 'browser_push'
identifier = params[:subscription_attributes][:device_id] if params[:subscription_type] == 'fcm'
```

## データベース操作仕様

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| create | notification_subscriptions | SELECT | 既存サブスクリプション検索 |
| create | notification_subscriptions | INSERT/UPDATE | サブスクリプション登録/更新 |
| destroy | notification_subscriptions | SELECT | 削除対象検索 |
| destroy | notification_subscriptions | DELETE | サブスクリプション削除 |

### テーブル別操作詳細

#### notification_subscriptions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | identifier = {識別子} | 既存検索 |
| INSERT | user_id, identifier, subscription_type, subscription_attributes | リクエストパラメータ | 新規作成 |
| UPDATE | user_id, subscription_attributes | リクエストパラメータ | 既存更新 |
| DELETE | - | subscription_attributes->>'push_token' = {push_token} | JSONBクエリ |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 401 | Unauthorized | 未認証状態でアクセス | ログインページへリダイレクト |
| 422 | UnprocessableEntity | バリデーションエラー | エラーメッセージを返却 |

### リトライ仕様

特になし。同期処理のため即時エラー返却。

## トランザクション仕様

- create: Railsのデフォルトトランザクション内で実行
- destroy: 個別トランザクションで実行

## パフォーマンス要件

- レスポンス時間: 100ms以内
- identifier カラムにユニークインデックスあり

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

- 認証必須: Devise TokenAuthによる認証
- サブスクリプション属性はJSONB形式で安全に保存
- プッシュトークンは機密情報として扱い、ログに出力しない

## 備考

- browser_push の endpoint はブラウザ固有のURLでVAPID認証に使用
- fcm の device_id はモバイルアプリのインストール固有識別子
- 同一デバイスで複数アカウントを切り替えるユースケースに対応

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | notification_subscription.rb | `app/models/notification_subscription.rb` | モデル構造とサブスクリプションタイプ |

**読解のコツ**:
- **1-17行目**: スキーマ情報（テーブル構造）
- **23-26行目**: `SUBSCRIPTION_TYPES` で browser_push=1、fcm=2 を定義
- subscription_attributes は JSONB 型で柔軟なデータ格納が可能

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | notification_subscriptions_controller.rb | `app/controllers/api/v1/notification_subscriptions_controller.rb` | API エンドポイント |

**主要処理フロー**:
- **4-7行目**: `create` - NotificationSubscriptionBuilder を使用
- **10-13行目**: `destroy` - push_token による検索と削除

#### Step 3: ビルダー層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | notification_subscription_builder.rb | `app/builders/notification_subscription_builder.rb` | サブスクリプション登録ロジック |

**主要処理フロー**:
- **4-9行目**: `perform` - メイン処理フロー
- **6行目**: 他ユーザーへのサブスクリプション移動
- **7行目**: 新規作成または更新の分岐
- **13-16行目**: `identifier` - タイプ別識別子の決定
- **23-24行目**: `move_subscription_to_user` - ユーザーID更新
- **26-28行目**: `build_identifier_subscription` - 新規作成
- **30-32行目**: `update_identifier_subscription` - 既存更新

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

```
NotificationSubscriptionsController
    │
    ├─ create
    │      └─ NotificationSubscriptionBuilder.new.perform
    │             │
    │             ├─ identifier_subscription (既存検索)
    │             │
    │             ├─ move_subscription_to_user (他ユーザー所有の場合)
    │             │      └─ update(user_id:)
    │             │
    │             ├─ build_identifier_subscription (新規の場合)
    │             │      └─ user.notification_subscriptions.create!
    │             │
    │             └─ update_identifier_subscription (既存の場合)
    │                    └─ identifier_subscription.update
    │
    └─ destroy
           ├─ NotificationSubscription.where (push_token検索)
           └─ notification_subscription.destroy!
```

### データフロー図

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

ブラウザ/アプリ ──────▶ NotificationSubscriptionsController ──▶ JSON Response
(subscription_type,               │
 subscription_attributes)         │
                                  ▼
                    NotificationSubscriptionBuilder
                                  │
                                  ▼
                    notification_subscriptions テーブル
                    (INSERT/UPDATE/DELETE)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| notification_subscription.rb | `app/models/notification_subscription.rb` | モデル | サブスクリプションデータモデル |
| notification_subscriptions_controller.rb | `app/controllers/api/v1/notification_subscriptions_controller.rb` | コントローラー | API エンドポイント |
| notification_subscription_builder.rb | `app/builders/notification_subscription_builder.rb` | ビルダー | サブスクリプション登録ロジック |
| api_base_controller.rb | `app/controllers/api/base_controller.rb` | コントローラー | 基底クラス（認証処理） |
