# 通知設計書 10-コメント報告通知

## 概要

本ドキュメントは、Ghost CMSにおけるコメント報告通知（Comment Report Notification）の設計仕様を記載する。会員がコメントを不適切として報告した際にサイトオーナーに送信される通知メールの送信ロジック、テンプレート構造、およびデータフローを定義する。

### 本通知の処理概要

本通知は、会員が投稿のコメントを不適切として報告した際に、サイトオーナーに対してメール通知を送信する機能である。報告対象のコメント内容と報告者の情報が含まれる。

**業務上の目的・背景**：コミュニティの健全性を維持するため、不適切なコメントを迅速に把握し対応できるようにする。サイトオーナーはコメントを確認し、必要に応じて削除やモデレーション対応を行う。

**通知の送信タイミング**：会員がコメントを報告した直後、CommentReportedEventがトリガーされた時点で送信される。

**通知の受信者**：サイトのオーナーユーザー（Owner role）にのみ送信される。他の管理者やエディターには送信されない。

**通知内容の概要**：メールには報告者の情報、報告されたコメントの内容（HTML/テキスト）、コメント投稿者の情報、投稿へのリンク（またはモデレーション画面へのリンク）が含まれる。

**期待されるアクション**：受信者は「View comment」または「Review comment」ボタンをクリックしてコメントを確認し、適切な対応（削除、非公開化など）を行う。

## 通知種別

メール通知（イベント駆動型通知）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（イベント駆動） |
| 優先度 | 高 |
| リトライ | 無 |

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

1. サイトのオーナーユーザーを取得（models.User.getOwnerUser()）
2. オーナーのメールアドレスに送信

## 通知テンプレート

### メール通知の場合

| 項目 | 内容 |
|-----|------|
| 送信元アドレス | settingsHelpers.getMembersSupportAddress() |
| 送信元名称 | サイトタイトル |
| 件名 | `A comment has been reported on your post` |
| 形式 | HTML + テキスト（マルチパート） |

### 本文テンプレート

```
件名: A comment has been reported on your post

本文:
Hey there,

{reporter} has reported the comment below on {postTitle}.
{{#unless commentModerationEnabled}}This comment will remain visible until you choose to remove it, which can be done directly on the post.{{/unless}}

[コメント投稿者アバター（イニシャル）]
{memberName}
{memberExpertise} - {commentDate}

{commentHtml}

{{#if commentModerationEnabled}}
[Review comment ボタン → モデレーション画面]
{{else}}
[View comment ボタン → 投稿ページ]
{{/if}}

---
You can also copy & paste this URL into your browser:
{postUrl}

---
This message was sent from {siteDomain} to {toEmail}
Manage your email preferences
```

### 添付ファイル

| ファイル名 | 形式 | 条件 | 説明 |
|----------|------|------|------|
| なし | - | - | 本通知には添付ファイルはない |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| siteTitle | サイト名 | settingsCache.get('title') | Yes |
| siteUrl | サイトURL | urlUtils.getSiteUrl() | Yes |
| siteDomain | サイトドメイン | urlUtils.getSiteUrl()から抽出 | Yes |
| postTitle | 投稿タイトル | post.get('title') | Yes |
| postUrl | コメント付き投稿URL | urlService.getUrlByResourceId() + '#ghost-comments-{commentId}' | Yes |
| commentHtml | コメント本文HTML | comment.get('html') | Yes |
| commentText | コメント本文テキスト | htmlToPlaintext.comment(comment.get('html')) | Yes |
| commentDate | コメント投稿日 | moment(comment.get('created_at')).format('D MMM YYYY') | Yes |
| reporterName | 報告者名 | reporter.name | No |
| reporterEmail | 報告者メールアドレス | reporter.email | Yes |
| reporter | 報告者表示名 | `{name} ({email})` または `{email}` | Yes |
| memberName | コメント投稿者名 | member.get('name') または 'Anonymous' | Yes |
| memberEmail | コメント投稿者メールアドレス | member.get('email') | Yes |
| memberExpertise | コメント投稿者の専門分野 | member.get('expertise') | No |
| memberInitials | コメント投稿者のイニシャル | extractInitials(memberName) | Yes |
| accentColor | サイトのアクセントカラー | settingsCache.get('accent_color') | No |
| fromEmail | 送信元アドレス | settingsHelpers.getMembersSupportAddress() | Yes |
| toEmail | 送信先アドレス | owner.get('email') | Yes |
| staffUrl | スタッフ設定URL | /settings/staff/{slug}/email-notifications | Yes |
| commentModerationEnabled | モデレーション機能有効 | labs.isSet('commentModeration') | Yes |
| moderationUrl | モデレーション画面URL | /comments/?id=is:{commentId} | No（commentModerationEnabled時のみ） |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| イベント | CommentReportedEvent | 常に送信（オーナーへ） | コメント報告 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| オーナーなし | オーナーユーザーが取得できない場合（通常発生しない） |
| コメントなし | 報告対象のコメントが見つからない場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[CommentReportedEvent発火] --> B[投稿情報取得]
    B --> C[コメント投稿者情報取得]
    C --> D[オーナーユーザー取得]
    D --> E{commentModeration有効?}
    E -->|Yes| F[moderationUrl設定]
    E -->|No| G[postUrlのみ使用]
    F --> H[テンプレートデータ構築]
    G --> H
    H --> I[report.hbsレンダリング]
    I --> J[オーナーにメール送信]
```

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

### 参照テーブル一覧

| テーブル名 | 用途 | 備考 |
|-----------|------|------|
| comments | 報告されたコメント取得 | イベントから取得 |
| posts | 投稿情報取得 | withRelated: ['authors'] |
| members | コメント投稿者情報取得 | comment.member_id |
| users | オーナーユーザー取得 | getOwnerUser() |
| settings | サイト設定取得 | settingsCacheから取得 |

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

#### users（オーナー）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| email | 送信先アドレス | - |
| slug | スタッフ設定URL生成 | - |

#### members（コメント投稿者）

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| name | コメント投稿者名 | - |
| email | コメント投稿者メールアドレス | - |
| expertise | 専門分野表示 | - |

#### comments

| 参照項目（カラム名） | 用途 | 取得条件 |
|-------------------|------|---------|
| id | コメント識別、URLフラグメント生成 | - |
| html | コメント本文HTML | - |
| created_at | コメント投稿日 | - |
| post_id | 投稿識別 | - |
| member_id | コメント投稿者識別 | - |

### 更新テーブル一覧

| テーブル名 | 操作 | 概要 |
|-----------|------|------|
| なし | - | 送信ログ等への書き込みは行われない |

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| PostNotFoundError | 投稿が見つからない | ログ出力、処理スキップ |
| MemberNotFoundError | コメント投稿者が見つからない | ログ出力、処理スキップ |
| OwnerNotFoundError | オーナーが見つからない | エラーログ出力 |
| MailError | メール送信失敗 | ログ出力（リトライなし） |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0 |
| リトライ間隔 | - |
| リトライ対象エラー | - |

## 配信設定

### レート制限

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

### 配信時間帯

特定の配信時間帯制限はない。報告直後に即座に送信。

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

- コメント本文HTML（commentHtml）は三重中括弧 `{{{commentHtml}}}` でエスケープせずに出力
- 報告者のメールアドレスがオーナーに開示される
- コメント投稿者のメールアドレスもオーナーに開示される
- オーナーのみに通知（他の管理者には送信されない）
- モデレーション画面URLは管理画面へのリンク（認証必要）

## 備考

- commentModeration labsフラグにより挙動が変わる
  - 有効時：「Review comment」ボタンでモデレーション画面へ遷移
  - 無効時：「View comment」ボタンで投稿ページへ遷移、「コメントは手動削除が必要」の注意文表示
- オーナーのみに通知する設計（将来的に他の管理者への通知拡張の可能性あり）
- htmlToPlaintext.comment()でHTMLをプレーンテキストに変換（テキストメール用）
- preheaderに報告者情報を表示

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | report.hbs | `ghost/core/core/server/services/comments/email-templates/report.hbs` | テンプレート構造、条件分岐 |
| 1-2 | comments-service-emails.js | `ghost/core/core/server/services/comments/comments-service-emails.js` | templateData構築（行159-183） |

**読解のコツ**: report.hbsにはcommentModerationEnabledによる条件分岐があり、ボタンのリンク先とメッセージが変わる点に注意。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | comments-service-emails.js | `ghost/core/core/server/services/comments/comments-service-emails.js` | notifyReport()メソッド |

**主要処理フロー**:
- **146-193行目**: notifyReport() - メイン送信処理
- **149行目**: オーナーユーザー取得（models.User.getOwnerUser()）
- **157行目**: commentModeration labsフラグ確認
- **166行目**: htmlToPlaintext.comment()でテキスト変換
- **169-171行目**: reporter表示名生成

#### Step 3: labsフラグの確認

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | labs設定 | Ghost Admin設定画面 | commentModerationフラグの有効化方法 |

**主要処理フロー**:
- labs.isSet('commentModeration')でフラグ確認
- 有効時はmoderationUrlを設定

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

```
CommentReportedEvent (Domain Event)
    |
    └─ Event Handler（イベント購読側）
           |
           └─ CommentsServiceEmails.notifyReport(comment, reporter)
                  |
                  ├─ models.Post.findOne({id: comment.post_id, withRelated: ['authors']})
                  |
                  ├─ models.Member.findOne({id: comment.member_id})
                  |
                  ├─ models.User.getOwnerUser()
                  |
                  ├─ labs.isSet('commentModeration')
                  |      |
                  |      ├─ true → moderationUrl設定
                  |      |
                  |      └─ false → commentModerationEnabled=false
                  |
                  ├─ htmlToPlaintext.comment(comment.html)
                  |
                  ├─ reporter表示名生成
                  |      |
                  |      ├─ reporter.name あり → `{name} ({email})`
                  |      |
                  |      └─ reporter.name なし → `{email}`
                  |
                  ├─ CommentsServiceEmailRenderer.renderEmailTemplate('report', data)
                  |      |
                  |      ├─ Handlebars.compile() - HTML生成
                  |      |
                  |      └─ report.txt.js - テキスト生成
                  |
                  └─ sendMail({to: owner.email, subject, html, text})
                         |
                         └─ mailer.send()
```

### データフロー図

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

CommentReportedEvent ──────▶ CommentsServiceEmails
  - comment                     |
  - reporter                    ▼
                          投稿情報取得
                                |
postsテーブル ◀───────────── 投稿検索
  - title                       |
  - authors                     ▼
                          コメント投稿者取得
                                |
membersテーブル ◀────────── member検索
  - name                        |
  - email                       ▼
  - expertise             オーナー取得
                                |
usersテーブル ◀────────── getOwnerUser()
  - email                       |
  - slug                        ▼
                          labsフラグ確認
                                |
labs設定 ─────────────▶        |
  - commentModeration           ▼
                          テンプレートデータ構築
                                |
                                ▼
                          HTML/テキスト変換
                                |
htmlToPlaintext ─────▶         |
                                ▼
                          Handlebarsレンダリング
                                |
settingsテーブル ────▶         |
  - title                       ▼
  - accent_color          メール送信 ─────────▶ オーナーのメールボックス
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| comments-service-emails.js | `ghost/core/core/server/services/comments/comments-service-emails.js` | サービス | メイン処理ロジック（行146-193） |
| comments-service-email-renderer.js | `ghost/core/core/server/services/comments/comments-service-email-renderer.js` | レンダラー | Handlebarsレンダリング |
| report.hbs | `ghost/core/core/server/services/comments/email-templates/report.hbs` | テンプレート | メールHTMLテンプレート |
| report.txt.js | `ghost/core/core/server/services/comments/email-templates/report.txt.js` | テンプレート | メールテキストテンプレート |
| html-to-plaintext | `@tryghost/html-to-plaintext` | ライブラリ | HTML→テキスト変換 |
