# 機能設計書 29-コメント通知

## 概要

本ドキュメントは、Ghostのコメント通知機能の設計仕様を記述する。コメント通知機能は、新しいコメントや返信が投稿された際に、関係者（記事の著者、親コメントの投稿者）にメールで通知を送信する。

### 本機能の処理概要

**業務上の目的・背景**：コメント通知は、コミュニティのエンゲージメントを維持するために重要な機能である。記事の著者は読者からのフィードバックを迅速に把握でき、コメントに返信があった場合はコメント投稿者も通知を受け取ることで、会話の継続性が促進される。

**機能の利用シーン**：
- 記事に新しいコメントが投稿された時（著者への通知）
- コメントに返信が投稿された時（親コメント投稿者への通知）
- 特定の返信に対して言及された時（言及先コメント投稿者への通知）

**主要な処理内容**：
1. 記事著者への新規コメント通知
2. 親コメント投稿者への返信通知
3. 言及先（in_reply_to）コメント投稿者への通知
4. 通知設定による送信制御

**関連システム・外部連携**：
- GhostMailer / CommentsServiceEmails
- DomainEvents（MemberCommentEvent）

**権限による制御**：
- 著者の通知設定（comment_notifications）
- メンバーの通知設定（enable_comment_notifications）

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 83 | コメントセクション（埋め込み） | トリガー | コメント・返信投稿時に通知発火 |
| - | スタッフ設定画面 | 関連 | 著者の通知設定 |
| - | Portal設定画面 | 関連 | メンバーの通知設定 |

## 機能種別

通知 / メール送信

## 入力仕様

### 入力パラメータ（新規コメント通知）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| comment | Model | Yes | コメントモデル | 有効なコメント |

### 入力パラメータ（返信通知）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| reply | Model | Yes | 返信コメントモデル | 有効なコメント |
| type | string | No | 'parent' または 'in_reply_to' | デフォルト: 'parent' |

### 入力データソース

- **comments テーブル**: コメントデータ
- **posts テーブル**: 記事データ（著者情報含む）
- **members テーブル**: メンバーデータ（通知設定含む）
- **users テーブル**: 著者データ（通知設定含む）

## 出力仕様

### 出力データ（通知メール）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| to | string | 送信先メールアドレス |
| subject | string | メール件名（絵文字付き） |
| html | string | HTMLメール本文 |
| text | string | テキストメール本文 |

### 出力先

- **メール**: 著者または親コメント投稿者へ送信

## 処理フロー

### 処理シーケンス（新規コメント通知）

```
1. コメント作成完了後
   └─ sendNewCommentNotifications() が呼び出される

2. 著者通知
   └─ notifyPostAuthors() 実行
   └─ 各著者の comment_notifications 設定を確認
   └─ 有効な場合のみメール送信

3. 返信通知（parent_id がある場合）
   └─ notifyParentCommentAuthor() 実行 (type: 'parent')
   └─ 親コメント投稿者の enable_comment_notifications を確認
   └─ 自己返信でない場合のみメール送信

4. 言及通知（in_reply_to_id がある場合）
   └─ notifyParentCommentAuthor() 実行 (type: 'in_reply_to')
   └─ 言及先コメント投稿者への通知
```

### フローチャート

```mermaid
flowchart TD
    A[コメント作成完了] --> B{内部コンテキスト?}
    B -->|Yes| C[通知スキップ]
    B -->|No| D[sendNewCommentNotifications]

    D --> E[notifyPostAuthors]
    E --> F{著者ループ}
    F --> G{comment_notifications ON?}
    G -->|No| H[スキップ]
    G -->|Yes| I[著者にメール送信]
    H --> F
    I --> F

    D --> J{parent_id あり?}
    J -->|No| K[終了]
    J -->|Yes| L[notifyParentCommentAuthor 'parent']
    L --> M{親コメント published?}
    M -->|No| K
    M -->|Yes| N{enable_comment_notifications ON?}
    N -->|No| K
    N -->|Yes| O{自己返信?}
    O -->|Yes| K
    O -->|No| P[親コメント投稿者にメール送信]

    D --> Q{in_reply_to_id あり?}
    Q -->|No| K
    Q -->|Yes| R[notifyParentCommentAuthor 'in_reply_to']
    R --> S[言及先にメール送信]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-29-01 | 著者通知設定 | comment_notifications が true の著者のみ通知 | 新規コメント時 |
| BR-29-02 | メンバー通知設定 | enable_comment_notifications が true のメンバーのみ通知 | 返信時 |
| BR-29-03 | 自己返信除外 | 自分のコメントへの返信には通知しない | 返信時 |
| BR-29-04 | 公開コメントのみ | status が 'published' のコメントへの返信のみ通知 | 返信時 |
| BR-29-05 | 内部操作除外 | context.internal が true の場合は通知をスキップ | Admin API経由のコメント作成時 |
| BR-29-06 | コメントパーマリンク | labs 'commentPermalinks' が有効な場合、コメント固有のURLを生成 | メールリンク生成時 |

### 計算ロジック

**イニシャル抽出**：
- 名前を空白で分割
- 最初と最後の単語の頭文字を取得
- 大文字に変換

```javascript
extractInitials(name) {
    const names = name.split(' ');
    const initials = names.length > 1
        ? [names[0][0], names[names.length - 1][0]]
        : [names[0][0]];
    return initials.join('').toUpperCase();
}
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 著者取得 | posts | SELECT | 記事の著者情報取得（withRelated: authors） |
| メンバー取得 | members | SELECT | コメント投稿者情報取得 |
| 親コメント取得 | comments | SELECT | 返信先コメント取得 |

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

#### users (著者)

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | email, comment_notifications, slug | post.authors の関連 | 通知判定と送信先 |

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | name, email, expertise, enable_comment_notifications, uuid | id で検索 | 通知判定と送信先 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | - | メール送信失敗 | ログ出力のみ（コメント作成は成功） |

### リトライ仕様

- メール送信失敗時のリトライなし
- 開発環境では送信内容をログに出力

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

- 通知送信はコメント作成トランザクション外で実行
- 送信失敗でもコメント作成は成功

## パフォーマンス要件

- 通知送信: 同期処理（レスポンス待ち）
- 複数著者への通知: 順次送信

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

- メンバーの購読解除URL生成（profileUrl）
- 著者の設定変更URL生成（staffUrl）
- 開発環境ではメール内容をログに出力（本番では無効）

## 備考

- メール件名に絵文字を使用（新規コメント: 💬、返信: ↪️）
- moment.js でタイムゾーン変換（settings.timezone）
- forceTextContent: true で HTML/テキスト両方を送信

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | comments-service.js | `ghost/core/core/server/services/comments/comments-service.js` | sendNewCommentNotifications メソッド（83-92行） |

**読解のコツ**: sendNewCommentNotifications は commentOnPost と replyToComment の両方から呼び出される。内部コンテキスト（options.context.internal）の場合はスキップされる。

#### Step 2: 通知ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | comments-service-emails.js | `ghost/core/core/server/services/comments/comments-service-emails.js` | 通知メール送信の実装 |

**主要処理フロー**:
1. **35-78行目**: notifyPostAuthors() - 著者への通知
2. **80-139行目**: notifyParentCommentAuthor() - 親コメント投稿者への通知
3. **27-33行目**: getPostUrl() - コメントURLの生成
4. **208-212行目**: extractInitials() - イニシャル抽出
5. **214-225行目**: sendMail() - メール送信の共通処理

#### Step 3: メールテンプレートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | new-comment.hbs | `ghost/core/core/server/services/comments/email-templates/new-comment.hbs` | 新規コメント通知HTMLテンプレート |
| 3-2 | new-comment-reply.hbs | `ghost/core/core/server/services/comments/email-templates/new-comment-reply.hbs` | 返信通知HTMLテンプレート |

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

```
CommentsService
    │
    ├─ commentOnPost()
    │      │
    │      └─ sendNewCommentNotifications()
    │             │
    │             ├─ CommentsServiceEmails.notifyPostAuthors()
    │             │      │
    │             │      ├─ Post.findOne({withRelated: ['authors']})
    │             │      │
    │             │      ├─ Member.findOne()
    │             │      │
    │             │      ├─ 著者ループ
    │             │      │      ├─ comment_notifications チェック
    │             │      │      ├─ commentsServiceEmailRenderer.renderEmailTemplate('new-comment')
    │             │      │      └─ sendMail()
    │             │      │
    │             │      └─ (parent_id あれば) notifyParentCommentAuthor()
    │             │
    │             └─ (in_reply_to_id あれば) notifyParentCommentAuthor()
    │
    └─ replyToComment()
           │
           └─ sendNewCommentNotifications()
                  │
                  ├─ notifyPostAuthors()
                  │
                  ├─ notifyParentCommentAuthor({type: 'parent'})
                  │      │
                  │      ├─ Comment.findOne({id: parent_id})
                  │      │
                  │      ├─ status === 'published' チェック
                  │      │
                  │      ├─ enable_comment_notifications チェック
                  │      │
                  │      ├─ 自己返信チェック
                  │      │
                  │      ├─ Post.findOne()
                  │      │
                  │      ├─ Member.findOne()
                  │      │
                  │      ├─ commentsServiceEmailRenderer.renderEmailTemplate('new-comment-reply')
                  │      │
                  │      └─ sendMail()
                  │
                  └─ notifyParentCommentAuthor({type: 'in_reply_to'})
```

### データフロー図

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

comment ─────────────────▶ sendNewCommentNotifications()
                                 │
                                 ▼
                          notifyPostAuthors()
                                 │
                                 ├─ Post.findOne() ─────────▶ 記事情報 + 著者リスト
                                 │
                                 ├─ Member.findOne() ───────▶ コメント投稿者情報
                                 │
                                 ├─ 著者ループ
                                 │      │
                                 │      ├─ comment_notifications チェック
                                 │      │
                                 │      └─ renderEmailTemplate('new-comment')
                                 │             │
                                 │             ├─ siteTitle, postTitle, postUrl
                                 │             ├─ commentHtml, commentDate
                                 │             ├─ memberName, memberInitials
                                 │             └─ staffUrl (設定変更リンク)
                                 │
                                 └─ sendMail() ─────────────▶ 著者へメール送信

comment (reply) ─────────▶ notifyParentCommentAuthor()
                                 │
                                 ├─ Comment.findOne() ──────▶ 親コメント情報
                                 │
                                 ├─ enable_comment_notifications チェック
                                 │
                                 ├─ 自己返信チェック
                                 │
                                 └─ renderEmailTemplate('new-comment-reply')
                                        │
                                        └─ profileUrl (購読解除リンク)
                                        │
                                        └─ sendMail() ──────▶ 親コメント投稿者へメール
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| comments-service.js | `ghost/core/core/server/services/comments/comments-service.js` | サービス | 通知トリガー |
| comments-service-emails.js | `ghost/core/core/server/services/comments/comments-service-emails.js` | サービス | 通知メール送信 |
| comments-service-email-renderer.js | `ghost/core/core/server/services/comments/comments-service-email-renderer.js` | サービス | テンプレートレンダリング |
| new-comment.hbs | `ghost/core/core/server/services/comments/email-templates/new-comment.hbs` | テンプレート | 新規コメント通知HTML |
| new-comment.txt.js | `ghost/core/core/server/services/comments/email-templates/new-comment.txt.js` | テンプレート | 新規コメント通知テキスト |
| new-comment-reply.hbs | `ghost/core/core/server/services/comments/email-templates/new-comment-reply.hbs` | テンプレート | 返信通知HTML |
| new-comment-reply.txt.js | `ghost/core/core/server/services/comments/email-templates/new-comment-reply.txt.js` | テンプレート | 返信通知テキスト |
