# 機能設計書 58-Webmention

## 概要

本ドキュメントは、GhostのWebmention機能に関する設計を記述します。この機能は、Webmention W3C標準プロトコルを使用して、サイト間のリンク通知を送受信し、投稿に対する言及を追跡・表示するための機能です。

### 本機能の処理概要

**業務上の目的・背景**：Webmentionは、他のサイトからの言及（リンク）を自動的に検出・表示するWeb標準です。これにより、ブログ間のコメントやトラックバックのような相互リンクを実現し、オープンウェブのコミュニケーションを促進します。

**機能の利用シーン**：
- 投稿公開時に記事内リンク先へWebmentionを送信
- 他サイトからのWebmentionを受信して言及として表示
- 投稿編集時に新規・削除リンクに対するWebmention送信

**主要な処理内容**：
1. Webmention受信（receive）とバックグラウンド処理
2. Webmention送信（send）と対象URL検出
3. メンション一覧の取得（browse）
4. ソースURLのメタデータ取得・検証
5. ターゲットリソースの特定と関連付け

**関連システム・外部連携**：
- 外部サイト: Webmentionエンドポイント検出・送受信
- Job Service: バックグラウンド処理

**権限による制御**：Webmentionの閲覧は認証不要（公開API）。プライベートモード時は送信を無効化。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 投稿詳細画面 | 参照画面 | メンション一覧の表示 |

## 機能種別

Webプロトコル / 受信処理 / 送信処理 / データ管理

## 入力仕様

### 入力パラメータ

#### Webmention受信

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| source | URL | Yes | 言及元URL | 有効なURL |
| target | URL | Yes | 言及先URL（自サイト） | 自サイトURL |
| payload | object | No | 追加データ | 任意 |

#### メンション一覧取得

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| filter | string | No | NQLフィルター | 有効なNQL |
| limit | number/all | No | 取得件数 | デフォルトall |
| page | number | No | ページ番号 | デフォルト1 |
| order | string | No | ソート順 | created_at asc/desc |
| unique | boolean | No | ソースURLで重複排除 | true/false |

### 入力データソース

- HTTPリクエスト: Webmention受信
- イベント: post.published, post.published.edited, post.unpublished
- データベース: `mentions`

## 出力仕様

### 出力データ

#### メンションレスポンス

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | メンションID |
| source | URL | 言及元URL |
| target | URL | 言及先URL |
| timestamp | Date | 受信日時 |
| verified | boolean | 検証済みフラグ |
| sourceTitle | string | ソースページタイトル |
| sourceSiteTitle | string | ソースサイトタイトル |
| sourceAuthor | string | ソース著者 |
| sourceExcerpt | string | ソース抜粋 |
| sourceFavicon | URL | ソースファビコン |
| sourceFeaturedImage | URL | ソースアイキャッチ画像 |
| resource | object | 関連リソース情報 |

### 出力先

- データベース: `mentions`
- 外部サイト: Webmention送信

## 処理フロー

### 処理シーケンス

```
1. Webmention受信
   └─ MentionController.receive()
   └─ ジョブキューに追加

2. バックグラウンド処理
   └─ MentionsAPI.processWebmention()
   └─ 既存メンション検索
   └─ ターゲット存在確認
   └─ ソースメタデータ取得
   └─ メンション作成/更新

3. 検証処理
   └─ mention.verify(html, contentType)
   └─ a[href], img[src], video[src]検索
   └─ verified=true/false設定

4. Webmention送信
   └─ MentionSendingService.sendForPost()
   └─ HTML内リンク抽出
   └─ 新規/削除リンクのみ処理
   └─ エンドポイント検出
   └─ POSTリクエスト送信
```

### フローチャート

```mermaid
flowchart TD
    A[Webmention受信] --> B[ジョブ追加]
    B --> C[processWebmention]
    C --> D{既存メンション?}
    D -->|Yes| E[更新処理]
    D -->|No| F{ターゲット存在?}
    F -->|No| G[BadRequestError]
    F -->|Yes| H[メタデータ取得]
    H --> I[メンション作成]
    I --> J[verify]
    E --> J
    J --> K[save]
    K --> L{新規?}
    L -->|Yes| M[MentionCreatedEvent]
    L -->|No| N[完了]
    M --> N
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-58-001 | ターゲット検証 | ターゲットURLが自サイトに存在すること | 受信時 |
| BR-58-002 | リンク検証 | ソースHTML内にターゲットへのリンクが存在すること | 検証時 |
| BR-58-003 | 削除処理 | 検証済みメンションがリンク削除された場合、deleted=trueに | 再検証時 |
| BR-58-004 | 新規/削除リンクのみ | 投稿編集時は新規・削除されたリンクのみWebmentionを送信 | 送信時 |
| BR-58-005 | 自サイト除外 | 自サイトへのリンクはWebmention送信しない | 送信時 |
| BR-58-006 | プライベートモード | is_private=trueの場合、送信を無効化 | 送信時 |

### 計算ロジック

**リンク検証（HTML）**:
```javascript
const hasTargetUrl = $('a[href*="' + target + '"], img[src*="' + target + '"], video[src*="' + target + '"]').length > 0;
verified = hasTargetUrl;
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| メンション作成 | mentions | INSERT | 新規メンション保存 |
| メンション更新 | mentions | UPDATE | 検証状態更新 |
| メンション検索 | mentions | SELECT | source+targetで検索 |
| 一覧取得 | mentions | SELECT | ページネーション付き一覧 |

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

#### mentions

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id, source, target, timestamp, payload, resource_id, resource_type, verified, deleted, source_* | 新規メンション | ObjectIDで生成 |
| UPDATE | verified, deleted, source_* | メタデータ更新 | source+targetで特定 |
| SELECT | * | filter, order, limit, page | ページネーション対応 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 400 | BadRequestError | ターゲットURLが自サイトに存在しない | 正しいURLを通知 |
| 400 | BadRequestError | Webmention送信失敗 | ログ出力（処理継続） |
| - | ValidationError | 無効なID/Date | エラーログ出力 |

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

メンションの作成・更新は単一のsave操作で完結するため、明示的なトランザクション管理は不要です。

## パフォーマンス要件

- Webmention受信応答: 50ms以内（ジョブキュー追加のみ）
- メンション一覧取得: 1秒以内
- Webmention送信: 15秒タイムアウト、3回リトライ

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

- ソースURLのメタデータ取得時は外部リクエストのタイムアウト設定
- メタデータ文字列は最大2000文字に制限
- source_is_ghost=trueフラグで送信元識別

## 備考

- Webmentionは W3C Recommendation（https://www.w3.org/TR/webmention/）
- 投稿公開/編集/非公開の全てでWebmention送信を実行

---

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | mention.js | `ghost/core/core/server/services/mentions/mention.js` | Mentionクラス、verify()メソッド |

#### Step 2: 初期化を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | service.js | `ghost/core/core/server/services/mentions/service.js` | init()、依存関係の組み立て |

#### Step 3: 受信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | mention-controller.js | `ghost/core/core/server/services/mentions/mention-controller.js` | receive(), browse() |
| 3-2 | mentions-api.js | `ghost/core/core/server/services/mentions/mentions-api.js` | processWebmention(), listMentions() |

#### Step 4: 送信処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | mention-sending-service.js | `ghost/core/core/server/services/mentions/mention-sending-service.js` | listen(), sendForPost(), send() |

**主要処理フロー**:
- **Mention 43-84行目**: `verify()` - リンク検証ロジック
- **Mention 238-327行目**: `create()` - メンション作成
- **Controller 123-137行目**: `receive()` - 受信エントリーポイント
- **API 270-277行目**: `processWebmention()` - メイン処理
- **SendingService 34-41行目**: `listen()` - イベント購読
- **SendingService 43-81行目**: `sendForPost()` - 送信トリガー
- **SendingService 87-115行目**: `send()` - 実際の送信

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

```
Webmention Receive:
    │
    └─ MentionController.receive(frame)
           │
           └─ jobService.addJob('processWebmention')
                  │
                  └─ MentionsAPI.processWebmention()
                         ├─ repository.getBySourceAndTarget()
                         ├─ routingService.pageExists()
                         ├─ resourceService.getByURL()
                         ├─ webmentionMetadata.fetch()
                         ├─ Mention.create() / mention.setSourceMetadata()
                         ├─ mention.verify()
                         └─ repository.save()

Webmention Send:
    │
    └─ MentionSendingService.listen(events)
           │
           └─ sendForPost(post)
                  │
                  └─ sendForHTMLResource()
                         ├─ getLinks(html)
                         └─ sendAll()
                                ├─ discoveryService.getEndpoint()
                                └─ send({source, target, endpoint})
```

### データフロー図

```
Receive Flow:
┌─────────────────┐     ┌───────────────────┐     ┌───────────────────┐
│  External Site  │────>│ MentionController │────>│  Job Queue        │
│  (Webmention)   │     │  (receive)        │     │  (background)     │
└─────────────────┘     └───────────────────┘     └───────────────────┘
                                                           │
                                                           ▼
┌─────────────────┐     ┌───────────────────┐     ┌───────────────────┐
│  mentions DB    │<────│  MentionsAPI      │<────│  Metadata Fetch   │
│  (save)         │     │  (process)        │     │  (source URL)     │
└─────────────────┘     └───────────────────┘     └───────────────────┘

Send Flow:
┌─────────────────┐     ┌───────────────────────┐     ┌───────────────────┐
│  Post Event     │────>│ MentionSendingService │────>│  Discovery        │
│  (published)    │     │  (sendForPost)        │     │  (getEndpoint)    │
└─────────────────┘     └───────────────────────┘     └───────────────────┘
                                                           │
                                                           ▼
                                                  ┌───────────────────┐
                                                  │  External Site    │
                                                  │  (POST form)      │
                                                  └───────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| mention.js | `ghost/core/core/server/services/mentions/mention.js` | ソース | Mentionエンティティ |
| mention-controller.js | `ghost/core/core/server/services/mentions/mention-controller.js` | ソース | APIコントローラー |
| mentions-api.js | `ghost/core/core/server/services/mentions/mentions-api.js` | ソース | ビジネスロジック |
| mention-sending-service.js | `ghost/core/core/server/services/mentions/mention-sending-service.js` | ソース | 送信サービス |
| mention-discovery-service.js | `ghost/core/core/server/services/mentions/mention-discovery-service.js` | ソース | エンドポイント検出 |
| bookshelf-mention-repository.js | `ghost/core/core/server/services/mentions/bookshelf-mention-repository.js` | ソース | リポジトリ |
| resource-service.js | `ghost/core/core/server/services/mentions/resource-service.js` | ソース | リソース特定 |
| routing-service.js | `ghost/core/core/server/services/mentions/routing-service.js` | ソース | URL存在確認 |
| webmention-metadata.js | `ghost/core/core/server/services/mentions/webmention-metadata.js` | ソース | メタデータ取得 |
| service.js | `ghost/core/core/server/services/mentions/service.js` | ソース | サービス初期化 |
