# 機能設計書 57-ActivityPub

## 概要

本ドキュメントは、GhostのActivityPub連携機能に関する設計を記述します。この機能は、Fediverse（Mastodon、Threads等の分散型SNS）との連携を実現し、投稿の公開・編集・削除をActivityPubプロトコルで配信するためのWebhook管理を行います。

### 本機能の処理概要

**業務上の目的・背景**：分散型ソーシャルネットワーク（Fediverse）の成長に伴い、GhostサイトをActivityPubに対応させることで、MastodonやThreads等からサイトをフォローし、投稿を受信できるようになります。これにより、既存のソーシャルメディアに依存せずに読者との繋がりを構築できます。

**機能の利用シーン**：サイト設定でSocial Webを有効化する場面、投稿を公開してFediverseに配信する場面、Fediverseからのフォロワーを管理する場面で活用されます。

**主要な処理内容**：
1. ActivityPub Webhookの初期化・管理
2. 投稿イベント（公開/編集/削除）のWebhook配信
3. サイトの有効化・無効化
4. Webhook秘密鍵の取得・管理
5. 設定変更時の自動再構成

**関連システム・外部連携**：
- Ghost ActivityPub Service: `.ghost/activitypub/` エンドポイント
- Fediverse: Mastodon, Threads等のActivityPub対応サービス
- Identity Token Service: 認証トークンの生成

**権限による制御**：ActivityPub機能の有効化・無効化はOwnerユーザーの認証トークンが必要です。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 43 | ActivityPub設定画面 | 主画面 | Social Webの有効化/無効化 |

## 機能種別

Webhook管理 / 外部連携 / 設定管理

## 入力仕様

### 入力パラメータ

本機能は設定変更イベントをトリガーとして動作します。

### 入力データソース

- 設定キャッシュ: `social_web_enabled` - Social Web有効化フラグ
- 設定キャッシュ: `is_private` - プライベートモードフラグ
- データベース: `integrations`, `webhooks`

## 出力仕様

### 出力データ

#### Webhook設定

| 項目名 | 型 | 説明 |
|--------|-----|------|
| event | string | イベント種別 |
| target_url | URL | Webhookエンドポイント |
| api_version | string | APIバージョン |
| secret | string | 署名用秘密鍵 |

### 出力先

- データベース: `webhooks`
- ActivityPub Service: `.ghost/activitypub/v1/site/`

## 処理フロー

### 処理シーケンス

```
1. サービス初期化
   └─ ActivityPubServiceWrapper.init()
   └─ IdentityTokenService確認

2. 設定監視
   └─ settings.labs.edited イベント購読
   └─ settings.social_web.edited イベント購読
   └─ settings.is_private.edited イベント購読

3. 有効化処理
   └─ enable() 呼び出し
   └─ initialiseWebhooks()
   └─ Webhook秘密鍵取得
   └─ 既存Webhook状態確認
   └─ 必要に応じて再作成

4. 無効化処理
   └─ disable() 呼び出し
   └─ removeWebhooks()
   └─ disableSite()
```

### フローチャート

```mermaid
flowchart TD
    A[設定変更イベント] --> B{social_web_enabled?}
    B -->|Yes| C{既に初期化済み?}
    B -->|No| D{初期化済み?}
    C -->|No| E[enable]
    C -->|Yes| F[スキップ]
    D -->|Yes| G[disable]
    D -->|No| F
    E --> H[initialiseWebhooks]
    H --> I[秘密鍵取得]
    I --> J[Webhook状態確認]
    J --> K{正しい状態?}
    K -->|Yes| L[完了]
    K -->|No| M[既存削除]
    M --> N[新規作成]
    N --> L
    G --> O[removeWebhooks]
    O --> P[disableSite]
    P --> L
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-57-001 | 前提条件 | IdentityTokenServiceが先に初期化されていること | サービス初期化時 |
| BR-57-002 | Webhook数 | 4つのWebhook（published, deleted, unpublished, updated）が必要 | 有効化時 |
| BR-57-003 | Webhook一致 | event, target_url, secretが全て一致すること | 状態確認時 |
| BR-57-004 | 状態不一致時 | 既存Webhookを全削除して新規作成 | 状態不一致時 |

### 計算ロジック

**Webhookエンドポイント生成**:
```javascript
new URL('.ghost/activitypub/v1/webhooks/post/{action}', siteUrl)
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 統合取得 | integrations | SELECT | ghost-activitypub統合の確認 |
| Webhook取得 | webhooks | SELECT | 既存Webhook一覧取得 |
| Webhook削除 | webhooks | DELETE | 既存Webhook削除 |
| Webhook作成 | webhooks | INSERT | 新規Webhook作成 |
| ユーザー取得 | users | SELECT | Ownerユーザー取得 |

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

#### webhooks

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | integration_id = ghost-activitypub | 状態確認用 |
| DELETE | - | integration_id = ghost-activitypub | 再作成前に削除 |
| INSERT | id, event, target_url, api_version, name, secret, integration_id, created_at | 4つのイベント用 | ObjectIDで生成 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | IdentityTokenService未初期化 | ログ出力後、初期化中止 |
| - | Error | ActivityPub統合なし | ログ出力後、初期化中止 |
| - | Error | Webhook秘密鍵取得失敗 | ログ出力後、初期化中止 |
| - | Error | サイト無効化失敗 | ログ出力（処理継続） |

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

Webhook削除と作成は別々のクエリで実行されます。途中でエラーが発生した場合、部分的な状態になる可能性があります（次回の状態確認で修正）。

## パフォーマンス要件

- Webhook初期化: 5秒以内
- 状態確認: 500ms以内

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

- Webhook秘密鍵はActivityPub Serviceから取得し、安全に保管
- Ownerユーザーの認証トークンを使用してAPI呼び出し
- プライベートモード時はActivityPubを無効化

## 備考

- APIバージョンは`v5.100.0`を使用
- 4つのイベントに対応: post.published, post.deleted, post.unpublished, post.published.edited

---

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

### 推奨読解順序

#### Step 1: サービス初期化を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | activity-pub-service-wrapper.js | `ghost/core/core/server/services/activitypub/activity-pub-service-wrapper.js` | init(), configureActivityPub() |

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | activity-pub-service.ts | `ghost/core/core/server/services/activitypub/activity-pub-service.ts` | enable(), disable(), initialiseWebhooks() |

**主要処理フロー**:
- **Wrapper 35-47行目**: `configureActivityPub()` - 設定に基づく有効化/無効化
- **Wrapper 49-51行目**: イベント購読登録
- **Service 27-54行目**: `getExpectedWebhooks()` - Webhook定義
- **Service 56-80行目**: `checkWebhookState()` - 状態確認
- **Service 129-179行目**: `initialiseWebhooks()` - Webhook初期化
- **Service 101-108行目**: `enable()` / `disable()` - 有効化/無効化

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

```
Boot Sequence
    │
    └─ ActivityPubServiceWrapper.init()
           │
           ├─ IdentityTokenServiceWrapper.instance 確認
           │
           ├─ ActivityPubService インスタンス作成
           │      ├─ knex
           │      ├─ siteUrl
           │      ├─ logging
           │      └─ identityTokenService
           │
           ├─ events.on('settings.labs.edited')
           ├─ events.on('settings.social_web.edited')
           ├─ events.on('settings.is_private.edited')
           │
           └─ configureActivityPub()
                  │
                  ├─ [enabled] ActivityPubService.enable()
                  │      └─ initialiseWebhooks()
                  │             ├─ getWebhookSecret()
                  │             ├─ checkWebhookState()
                  │             └─ webhooks INSERT
                  │
                  └─ [disabled] ActivityPubService.disable()
                         ├─ removeWebhooks()
                         └─ disableSite()
```

### データフロー図

```
┌─────────────────┐     ┌──────────────────────────┐
│  Settings       │────>│ ActivityPubServiceWrapper │
│  (events)       │     │  (configureActivityPub)   │
└─────────────────┘     └──────────────────────────┘
                                    │
                                    ▼
                        ┌──────────────────────────┐
                        │   ActivityPubService     │
                        │   (enable/disable)       │
                        └──────────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    ▼               ▼               ▼
        ┌───────────────┐   ┌───────────────┐   ┌───────────────┐
        │  webhooks     │   │  integrations │   │ ActivityPub   │
        │  (Database)   │   │  (Database)   │   │ Service API   │
        └───────────────┘   └───────────────┘   └───────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| activity-pub-service-wrapper.js | `ghost/core/core/server/services/activitypub/activity-pub-service-wrapper.js` | ソース | サービス初期化・イベント購読 |
| activity-pub-service.ts | `ghost/core/core/server/services/activitypub/activity-pub-service.ts` | ソース | Webhook管理ロジック |
| index.js | `ghost/core/core/server/services/activitypub/index.js` | ソース | モジュールエクスポート |
| identity-token-service.ts | `ghost/core/core/server/services/identity-tokens/identity-token-service.ts` | ソース | 認証トークン生成 |
