# 機能設計書 53-同期サーバー

## 概要

本ドキュメントは、Actual Budgetにおける同期サーバー機能の設計を記述する。この機能は、複数クライアント間でのデータ同期を管理するバックエンドサーバーを提供し、CRDTベースの差分同期を実現する。

### 本機能の処理概要

Actual Budgetの同期サーバーは、Express.jsベースのHTTPサーバーとして実装されており、複数のデバイス間で予算データを安全に同期するための中核機能を提供する。Protocol Buffersを使用した効率的なバイナリ通信、Merkle Trieを用いた差分検出、E2E暗号化のサポートなど、高度な同期メカニズムを実装している。

**業務上の目的・背景**：ユーザーが複数のデバイス（デスクトップ、モバイル等）からActual Budgetにアクセスする際に、一貫した予算データを維持する必要がある。また、オフライン時でも作業を継続でき、オンライン復帰時に自動的に同期することで、ユーザー体験を向上させる。

**機能の利用シーン**：
- 新しいデバイスでの予算ファイル取得
- 複数デバイス間でのデータ同期
- 予算ファイルのクラウドバックアップ
- 共有予算へのアクセス

**主要な処理内容**：
1. 予算ファイルのアップロード・ダウンロード
2. CRDTメッセージの送受信による差分同期
3. Merkle Trieを使用した効率的な差分検出
4. ユーザー認証・セッション管理
5. ファイル暗号化キーの管理
6. ファイルアクセス権限の管理

**関連システム・外部連携**：
- 銀行連携サービス（GoCardless、SimpleFIN、PluggyAI）
- OpenID認証プロバイダ
- SQLiteデータベース

**権限による制御**：ファイルオーナーと管理者のみがファイルを削除可能。ファイルへのアクセスは所有者または共有されたユーザーに限定。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 1 | ブートストラップ画面 | 主画面 | 初回起動時のサーバーURL設定・接続確認処理 |
| 6 | サーバー設定画面 | 主画面 | サーバーURL設定・接続設定の変更処理 |
| 8 | 予算ファイル選択画面 | 主画面 | 既存予算ファイルの一覧表示・選択処理 |
| 43 | 致命的エラー画面 | 遷移先画面 | 致命的エラー発生時の表示・復旧オプション提供 |

## 機能種別

サーバー機能 / データ同期 / CRUD操作

## 入力仕様

### 入力パラメータ（同期リクエスト）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| fileId | string | Yes | 予算ファイルID | UUIDフォーマット |
| groupId | string | Yes | 同期グループID | UUIDフォーマット |
| keyId | string | No | 暗号化キーID | 暗号化使用時必須 |
| since | string | Yes | 最終同期タイムスタンプ | HULCタイムスタンプ形式 |
| messages | MessageEnvelope[] | No | 同期メッセージ配列 | Protocol Buffer形式 |

### 入力データソース

- HTTPリクエスト（REST API）
- Protocol Bufferシリアライズデータ

## 出力仕様

### 出力データ（同期レスポンス）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| merkle | string | 更新後のMerkle Trie（JSON文字列） |
| messages | MessageEnvelope[] | 新規メッセージ配列 |

### 出力先

- HTTPレスポンス（REST API）
- Protocol Bufferシリアライズデータ

## 処理フロー

### 処理シーケンス

```
1. 同期リクエスト受信
   └─ POST /sync エンドポイント

2. Protocol Buffer デシリアライズ
   └─ SyncRequest.deserializeBinary

3. ファイル存在確認
   └─ FilesService.get(fileId)

4. 同期権限検証
   └─ validateSyncedFile

5. メッセージ処理
   ├─ 受信メッセージをDBに保存
   └─ Merkle Trieの更新

6. 差分メッセージ取得
   └─ since以降のメッセージ取得

7. レスポンス構築
   └─ SyncResponse.serializeBinary

8. レスポンス送信
   └─ Content-Type: application/actual-sync
```

### フローチャート

```mermaid
flowchart TD
    A[POST /sync 受信] --> B[Protocol Buffer デシリアライズ]
    B --> C{デシリアライズ成功?}
    C -->|No| D[500 Internal Error]
    C -->|Yes| E{since パラメータあり?}
    E -->|No| F[422 Unprocessable Entity]
    E -->|Yes| G[ファイル存在確認]
    G --> H{ファイル存在?}
    H -->|No| I[400 File Not Found]
    H -->|Yes| J[同期権限検証]
    J --> K{権限OK?}
    K -->|No| L[400 Error]
    K -->|Yes| M[simpleSync.sync実行]
    M --> N[Merkle Trie更新]
    N --> O[新規メッセージ取得]
    O --> P[SyncResponse構築]
    P --> Q[レスポンス送信]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-53-1 | セッション必須 | 全ての同期操作にはセッショントークンが必要 | 全リクエスト |
| BR-53-2 | ファイル所有者確認 | ファイル削除は所有者または管理者のみ | ファイル削除時 |
| BR-53-3 | グループID自動生成 | 新規ファイルアップロード時にgroupIdを自動生成 | 新規アップロード時 |
| BR-53-4 | 暗号化キー整合性 | 暗号化ファイルは正しいkeyIdでのみ同期可能 | 暗号化ファイル同期時 |

### 計算ロジック

- Merkle Trie: タイムスタンプをBase3変換してトライ構造に挿入
- ハッシュ計算: MurmurHash v3を使用

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| 同期 | messages_binary | INSERT | 同期メッセージの保存 |
| 同期 | messages_merkles | UPDATE | Merkle Trieの更新 |
| ファイル一覧 | files | SELECT | ユーザーファイル一覧取得 |
| ファイル更新 | files | UPDATE | ファイルメタデータ更新 |
| ファイル削除 | files | UPDATE | 論理削除フラグ設定 |

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

#### messages_binary

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | timestamp | HULCタイムスタンプ | 一意制約あり |
| INSERT | is_encrypted | 0 or 1 | 暗号化状態 |
| INSERT | content | バイナリデータ | メッセージ本体 |

#### messages_merkles

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPSERT | id | 1 | 固定値 |
| UPSERT | merkle | JSONシリアライズされたMerkle Trie | - |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| 400 | Bad Request | ファイルが見つからない | クライアントにエラー通知 |
| 403 | Forbidden | ファイル削除権限なし | クライアントにエラー通知 |
| 422 | Unprocessable Entity | sinceパラメータなし | クライアントにエラー通知 |
| 500 | Internal Error | Protocol Bufferパースエラー | クライアントにエラー通知 |

### リトライ仕様

サーバー側でのリトライは実装されていない。クライアント側で同期失敗時にリトライを実行。

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

- メッセージ挿入とMerkle Trie更新はSQLiteトランザクション内で実行
- ファイルアップロードとメタデータ更新は非トランザクション

## パフォーマンス要件

- 同期レスポンス時間：通常1秒以内
- ファイルサイズ制限：設定可能（デフォルト20MB）

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

- セッションベースの認証
- E2E暗号化のサポート
- レート制限の適用
- CORS設定による不正アクセス防止
- ディレクトリトラバーサル対策

## 備考

- デスクトップアプリではElectronから内蔵同期サーバーを起動可能
- Protocol Buffersによる効率的なバイナリ通信

---

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

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

### 推奨読解順序

#### Step 1: サーバー起動とルーティングを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app.ts | `packages/sync-server/src/app.ts` | サーバー構成とルーティング設定 |

**読解のコツ**: Express.jsのミドルウェアパターンを理解しておくと、処理の流れが把握しやすい。

**主要処理フロー**:
- **21行目**: Expressアプリケーション作成
- **27-28行目**: CORS設定
- **30-38行目**: レート制限設定
- **57-69行目**: 各種ルーターのマウント
- **116-118行目**: ヘルスチェックエンドポイント
- **174-211行目**: サーバー起動処理

#### Step 2: 同期処理の詳細を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | app-sync.ts | `packages/sync-server/src/app-sync.ts` | 同期APIエンドポイント実装 |

**主要処理フロー**:
- **71-127行目**: POST /sync エンドポイント
- **74行目**: Protocol Buffer デシリアライズ
- **82-86行目**: リクエストパラメータ取得
- **117行目**: simpleSync.sync呼び出し
- **120-126行目**: レスポンス構築

#### Step 3: 差分同期ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | sync-simple.js | `packages/sync-server/src/sync-simple.js` | 差分同期の実装 |

**主要処理フロー**:
- **72-95行目**: sync関数
- **24-58行目**: addMessages関数（メッセージ追加とMerkle更新）

#### Step 4: 設定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | load-config.js | `packages/sync-server/src/load-config.js` | サーバー設定スキーマ |

**主要処理フロー**:
- **64-301行目**: convictスキーマ定義
- **86-92行目**: ポート設定
- **154-170行目**: HTTPS設定
- **291-300行目**: CORSプロキシ設定

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

```
app.ts (Express Server)
    │
    ├─ app-sync.ts (/sync)
    │      │
    │      ├─ SyncProtoBuf.SyncRequest.deserializeBinary()
    │      │
    │      ├─ FilesService.get()
    │      │
    │      ├─ validateSyncedFile()
    │      │
    │      ├─ simpleSync.sync()
    │      │      │
    │      │      ├─ getGroupDb()
    │      │      ├─ addMessages()
    │      │      │      ├─ getMerkle()
    │      │      │      └─ merkle.insert()
    │      │      └─ db.all() [新規メッセージ取得]
    │      │
    │      └─ SyncProtoBuf.SyncResponse.serializeBinary()
    │
    ├─ app-account.ts (/account)
    │
    └─ app-admin.ts (/admin)
```

### データフロー図

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

クライアント ───────▶ POST /sync ───────▶ messages_binary DB
(Protocol Buffer)          │
                           ▼
                    Merkle Trie更新
                           │
                           ▼
                    差分メッセージ取得
                           │
                           ▼
                    SyncResponse構築 ───────▶ クライアント
                                            (Protocol Buffer)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app.ts | `packages/sync-server/src/app.ts` | ソース | Express サーバー本体 |
| app-sync.ts | `packages/sync-server/src/app-sync.ts` | ソース | 同期APIエンドポイント |
| sync-simple.js | `packages/sync-server/src/sync-simple.js` | ソース | 差分同期ロジック |
| load-config.js | `packages/sync-server/src/load-config.js` | ソース | サーバー設定 |
| sync_pb.ts | `packages/crdt/src/proto/sync_pb.ts` | ソース | Protocol Buffer型定義 |
| merkle.ts | `packages/crdt/src/crdt/merkle.ts` | ソース | Merkle Trie実装 |
| timestamp.ts | `packages/crdt/src/crdt/timestamp.ts` | ソース | HULCタイムスタンプ実装 |
