# 機能設計書 56-オーディエンスフィードバック

## 概要

本ドキュメントは、Ghostのオーディエンスフィードバック機能に関する設計を記述します。この機能は、読者が投稿に対して「良い」「悪い」のフィードバックを送信できるようにし、パブリッシャーがコンテンツの反響を把握するための機能です。

### 本機能の処理概要

**業務上の目的・背景**：ニュースレターやブログ投稿に対する読者の反応を収集することで、コンテンツの質を向上させ、読者のニーズに応えるコンテンツ制作が可能になります。シンプルな二択のフィードバック（👍/👎）により、読者は手軽に意見を表明できます。

**機能の利用シーン**：ニュースレター購読者がメール内のフィードバックリンクをクリックする場面、投稿閲覧後にサイト上でフィードバックを送信する場面で活用されます。

**主要な処理内容**：
1. フィードバックリンクの生成（メール用）
2. フィードバックの受信と保存
3. 既存フィードバックの更新（スコア変更）
4. 投稿別フィードバック一覧の取得
5. フィードバック統計の集計

**関連システム・外部連携**：
- メール配信システム: フィードバックリンクの埋め込み
- Members API: フィードバック送信

**権限による制御**：フィードバック送信は認証済みメンバーのみ。フィードバック一覧取得は `posts` ドキュメントの `browse` 権限を持つユーザーに限定されます。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 30 | 投稿ニュースレター分析画面 | 主画面 | フィードバック統計の表示 |

## 機能種別

データ登録 / データ更新 / 集計処理

## 入力仕様

### 入力パラメータ

#### フィードバック送信

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| post_id | string | Yes | 投稿ID | 有効な投稿ID |
| score | number | Yes | フィードバックスコア | 0または1のみ |

#### フィードバック一覧取得

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | string | Yes | 投稿ID | 有効な投稿ID |
| limit | number | No | 取得件数 | デフォルト10 |
| page | number | No | ページ番号 | デフォルト1 |
| score | number | No | スコアフィルター | 0または1 |

### 入力データソース

- Members API: フィードバックリクエスト
- メールリンク: uuid, postId, score, key（署名）
- データベース: `members_feedback`, `members`, `posts`

## 出力仕様

### 出力データ

#### フィードバック送信レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | フィードバックID |
| score | number | フィードバックスコア（0または1） |
| memberId | string | メンバーID |
| postId | string | 投稿ID |

#### フィードバック一覧レスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| data[].id | string | フィードバックID |
| data[].score | number | フィードバックスコア |
| data[].created_at | string | 作成日時 |
| data[].member | object | メンバー情報 |
| meta | object | ページネーション情報 |

### 出力先

- データベース: `members_feedback`
- Admin API: JSON形式でレスポンス

## 処理フロー

### 処理シーケンス

```
1. フィードバック送信リクエスト
   └─ Members API /feedback/

2. バリデーション
   └─ score: 0または1のみ許可
   └─ 投稿の存在確認
   └─ メンバー認証確認

3. 既存フィードバック確認
   └─ 同一メンバー・投稿の組み合わせを検索

4. フィードバック保存
   └─ 既存: スコアが異なれば更新
   └─ 新規: 新しいフィードバックを作成

5. レスポンス返却
```

### フローチャート

```mermaid
flowchart TD
    A[フィードバック送信] --> B{score有効?}
    B -->|No| C[ValidationError]
    B -->|Yes| D{投稿存在?}
    D -->|No| E[NotFoundError]
    D -->|Yes| F{既存フィードバック?}
    F -->|Yes| G{スコア変更?}
    F -->|No| H[新規作成]
    G -->|Yes| I[更新]
    G -->|No| J[既存返却]
    H --> K[レスポンス]
    I --> K
    J --> K
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-56-001 | スコア制限 | 0（ネガティブ）または1（ポジティブ）のみ許可 | フィードバック送信時 |
| BR-56-002 | 一意制約 | 1メンバー1投稿につき1フィードバック | 全てのフィードバック |
| BR-56-003 | 重複更新回避 | スコアが同じ場合はupdated_atを更新しない | 既存フィードバック更新時 |
| BR-56-004 | 404フォールバック | 投稿URLが404の場合はbaseURLにリダイレクト | リンク生成時 |

### 計算ロジック

**フィードバック統計**:
```sql
positive_feedback = COUNT(*) WHERE score = 1
negative_feedback = COUNT(*) WHERE score = 0
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| フィードバック作成 | members_feedback | INSERT | 新規フィードバック保存 |
| フィードバック更新 | members_feedback | UPDATE | スコア変更 |
| フィードバック取得 | members_feedback | SELECT | 既存フィードバック確認 |
| 一覧取得 | members_feedback | SELECT | 投稿別フィードバック一覧 |

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

#### members_feedback

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id, member_id, post_id, score | 新規フィードバック | ObjectIDで生成 |
| UPDATE | score | 変更後スコア | IDで特定 |
| SELECT | * | member_id, post_id で検索 | withRelated: member |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 400 | ValidationError | 無効なスコア（0,1以外） | 有効なスコアを通知 |
| 404 | NotFoundError | 投稿が存在しない | 正しい投稿IDを通知 |
| 500 | InternalServerError | メンバー認証なし | 認証状態を確認 |

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

フィードバックの作成・更新は個別のデータベース操作で完結するため、明示的なトランザクション管理は不要です。

## パフォーマンス要件

- フィードバック送信: 100ms以内
- フィードバック一覧取得: 500ms以内

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

- メールリンクにはキー（ハッシュ）を含めて改ざん防止
- フィードバック送信は認証済みメンバーのみ
- 他メンバーのフィードバックは更新不可

## 備考

- フィードバックカウントはposts APIの`count.positive_feedback`/`count.negative_feedback`で取得可能

---

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | feedback.js | `ghost/core/core/server/services/audience-feedback/feedback.js` | Feedbackクラス定義 |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | feedback-members.js | `ghost/core/core/server/api/endpoints/feedback-members.js` | add, browse API |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | audience-feedback-controller.js | `ghost/core/core/server/services/audience-feedback/audience-feedback-controller.js` | add, browse ロジック |
| 3-2 | audience-feedback-service.js | `ghost/core/core/server/services/audience-feedback/audience-feedback-service.js` | リンク生成 |
| 3-3 | feedback-repository.js | `ghost/core/core/server/services/audience-feedback/feedback-repository.js` | データアクセス |

**主要処理フロー**:
- **Controller 45-83行目**: `add()` - フィードバック追加/更新
- **Controller 85-98行目**: `browse()` - 一覧取得
- **Service 22-31行目**: `buildLink()` - メール用リンク生成
- **Repository 28-35行目**: `add()` - データベース保存
- **Repository 68-98行目**: `getForPost()` - 投稿別一覧取得

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

```
Members API Request (/feedback/)
    │
    └─ feedback-members.js (API Controller)
           │
           ├─ add (POST)
           │      └─ AudienceFeedbackController.add()
           │             ├─ getMember(frame)
           │             ├─ repository.getPostById(postId)
           │             ├─ repository.get(postId, memberId)
           │             └─ repository.add/edit(feedback)
           │
           └─ browse (GET)
                  └─ AudienceFeedbackController.browse()
                         └─ repository.getForPost(postId, options)
```

### データフロー図

```
┌─────────────────┐     ┌──────────────────────────┐
│  Members API    │────>│ AudienceFeedbackController│
│  (feedback-    │     │  (add/browse)             │
│   members.js)   │     └──────────────────────────┘
└─────────────────┘                  │
                                     ▼
                        ┌──────────────────────────┐
                        │   FeedbackRepository     │
                        │   (CRUD operations)      │
                        └──────────────────────────┘
                                     │
                                     ▼
                        ┌──────────────────────────┐
                        │   members_feedback       │
                        │   (Database)             │
                        └──────────────────────────┘

Mail Link Generation:
┌─────────────────┐     ┌──────────────────────────┐
│  Email Service  │────>│ AudienceFeedbackService  │
│  (newsletter)   │     │  (buildLink)             │
└─────────────────┘     └──────────────────────────┘
                                     │
                                     ▼
                        ┌──────────────────────────┐
                        │  Feedback URL            │
                        │  (#{postUrl}/feedback/   │
                        │   {postId}/{score}/...)  │
                        └──────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| feedback-members.js | `ghost/core/core/server/api/endpoints/feedback-members.js` | ソース | API エンドポイント |
| audience-feedback-controller.js | `ghost/core/core/server/services/audience-feedback/audience-feedback-controller.js` | ソース | ビジネスロジック |
| audience-feedback-service.js | `ghost/core/core/server/services/audience-feedback/audience-feedback-service.js` | ソース | リンク生成サービス |
| feedback-repository.js | `ghost/core/core/server/services/audience-feedback/feedback-repository.js` | ソース | データアクセス |
| feedback.js | `ghost/core/core/server/services/audience-feedback/feedback.js` | ソース | Feedbackモデル |
| index.js | `ghost/core/core/server/services/audience-feedback/index.js` | ソース | サービス初期化 |
