# 機能設計書 44-ファイル取得

## 概要

本ドキュメントは、GitHubリポジトリからスキーマファイルを取得する機能の設計について記載する。この機能は、ブランチやコミット指定に対応したファイル取得メカニズムを提供し、ER図生成に必要なスキーマファイルの読み込みを実現する。

### 本機能の処理概要

**業務上の目的・背景**：Liamアプリケーションは、ユーザーのGitHubリポジトリに格納されたスキーマファイル（schema.rb、schema.prisma、DDLファイル等）を取得し、ER図として可視化する機能を提供する。異なるブランチやコミットのスキーマを比較・参照できるよう、柔軟なファイル取得機能が必要である。

**機能の利用シーン**：
- プロジェクトのスキーマ画面表示時にスキーマファイルを取得
- ブランチ切り替え時に該当ブランチのスキーマファイルを取得
- 特定コミットのスキーマファイルを参照する場合
- 公開リポジトリからスキーマファイルを取得する場合

**主要な処理内容**：
1. リポジトリ名、ファイルパス、ref（ブランチ/コミット）の指定
2. GitHub App認証によるAPIアクセス
3. Base64エンコードされたファイル内容のデコード
4. ファイル内容とSHA（ハッシュ値）の返却
5. 公開リポジトリからのRaw URLダウンロード

**関連システム・外部連携**：
- GitHub REST API（/repos/{owner}/{repo}/contents/{path}）
- raw.githubusercontent.com（公開リポジトリのファイルダウンロード）

**権限による制御**：GitHub Appがインストールされたリポジトリのみアクセス可能。公開リポジトリは認証なしでアクセス可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 12 | スキーマ画面 | 主画面 | GitHubからスキーマファイルを取得 |
| 10 | ブランチ詳細画面 | 参照画面 | ブランチ指定でのファイル取得 |
| 20 | 公開ERD画面 | 参照画面 | 公開リポジトリからのファイル取得 |

## 機能種別

外部API連携 / データ取得

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| repositoryFullName | string | Yes | 「owner/repo」形式のフルネーム | `/`区切りで2要素 |
| filePath | string | Yes | 取得対象ファイルパス | 空文字不可 |
| ref | string | Yes | ブランチ名またはコミットSHA | - |
| installationId | number | Yes | GitHub Appインストール識別子 | 正の整数 |

**downloadFileContent用パラメータ**

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| url | string | Yes | ダウンロード対象URL | raw.githubusercontent.comのみ許可 |
| timeoutMs | number | No | タイムアウト時間（デフォルト: 10000ms） | 正の整数 |
| maxBytes | number | No | 最大ファイルサイズ | 正の整数 |

### 入力データソース

- データベースに保存されたプロジェクト情報（repositoryFullName、schemaPath）
- URLパラメータまたはルーティング（ref）
- GitHub App Installation情報

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| content | string \| null | ファイル内容（UTF-8文字列） |
| sha | string \| null | ファイルのGit SHA（ハッシュ値） |

### 出力先

呼び出し元への戻り値として返却、スキーマパーサーへの入力として使用

## 処理フロー

### 処理シーケンス

```
1. 入力パラメータの検証
   └─ repositoryFullNameを owner/repo に分割
2. 分割結果の検証
   ├─ 失敗時 → { content: null, sha: null } を返却
   └─ 成功時 → 次のステップへ
3. createOctokit呼び出し
   └─ installationIdを使用して認証付きクライアント生成
4. repos.getContent API呼び出し
   └─ owner, repo, path, ref を指定
5. レスポンス検証
   ├─ type === 'file' かつ content存在
   │      └─ Base64デコードして返却
   └─ それ以外 → { content: null, sha: null }
6. エラーハンドリング
   ├─ 404エラー → ログ出力のみ、null返却
   └─ その他エラー → エラーログ、null返却
```

### フローチャート

```mermaid
flowchart TD
    A[getFileContent呼び出し] --> B[repositoryFullName分割]
    B --> C{分割成功?}
    C -->|No| D[null返却]
    C -->|Yes| E[createOctokit]
    E --> F[repos.getContent API]
    F --> G{API成功?}
    G -->|No| H{404エラー?}
    H -->|Yes| I[info ログ出力]
    H -->|No| J[error ログ出力]
    I --> D
    J --> D
    G -->|Yes| K{type === file?}
    K -->|No| L[warn ログ出力]
    L --> D
    K -->|Yes| M[Base64デコード]
    M --> N[content, sha 返却]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-44-01 | リポジトリ名形式 | 「owner/repo」形式でなければエラー | 全ての取得操作 |
| BR-44-02 | ファイルタイプ確認 | type === 'file'の場合のみ内容を返却 | レスポンス処理時 |
| BR-44-03 | Base64デコード | GitHub APIはBase64でファイル内容を返却 | ファイル取得成功時 |
| BR-44-04 | 404静音処理 | ファイル不在は正常系として扱う（infoログのみ） | 404レスポンス時 |
| BR-44-05 | SHA返却 | ファイルのGit SHAを同時に返却 | ファイル取得成功時 |
| BR-44-06 | URL制限 | ダウンロードURLはraw.githubusercontent.comのみ許可 | downloadFileContent使用時 |

### 計算ロジック

**リポジトリ名分割**：
```typescript
const [owner, repo] = repositoryFullName.split('/')
if (!owner || !repo) {
  return { content: null, sha: null }
}
```

**Base64デコード**：
```typescript
Buffer.from(data.content, 'base64').toString('utf-8')
```

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | 本機能はデータベースを直接操作しない |

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

本機能はGitHub APIを呼び出すユーティリティであり、データベースを直接操作しない。
取得したスキーマファイルは、呼び出し元でスキーマパーサーに渡されて処理される。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | リポジトリ形式エラー | owner/repo形式でない | nullを返却 |
| 404 | ファイル不在 | 指定パスにファイルが存在しない | infoログ出力、nullを返却 |
| 404 | ブランチ/コミット不在 | 指定refが存在しない | infoログ出力、nullを返却 |
| - | 非ファイル | 取得対象がディレクトリ等 | warnログ出力、nullを返却 |
| - | タイムアウト | ダウンロード時間超過 | AbortError、エラー返却 |
| - | ファイルサイズ超過 | maxBytes制限超過 | エラー返却 |
| - | 不正URL | raw.githubusercontent.com以外 | エラー返却 |

### リトライ仕様

本機能自体はリトライを行わない。ネットワークエラー等の一時的な障害に対しては、呼び出し元でretry関数（No.41）を使用することを推奨。

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

本機能はGitHub APIへの読み取り操作のみであり、トランザクション管理は不要。

## パフォーマンス要件

- GitHub API応答時間に依存（通常100-500ms）
- ファイルダウンロード: タイムアウト10秒（デフォルト）
- 大規模ファイルは maxBytes パラメータで制限可能

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

- **GitHub App認証**: Private Keyによるセキュアな認証
- **インストールID制限**: インストール済みリポジトリのみアクセス可能
- **URL検証**: raw.githubusercontent.comのみ許可（SSRF対策）
- **入力検証**: repositoryFullNameの形式チェック

## 備考

- @octokit/restライブラリを使用
- ファイル内容はBase64エンコードでGitHub APIから返却される
- SHAはファイルの一意識別に使用（キャッシュキー等）
- 公開リポジトリへのアクセスは認証不要

---

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

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

### 推奨読解順序

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

まず、ファイル取得の戻り値構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `frontend/internal-packages/github/src/types.ts` | GitHubContentItem型の定義 |

**読解のコツ**: download_urlプロパティがnullableであることに注目。ファイルタイプによって存在有無が変わる。

#### Step 2: getFileContent関数を理解する

ファイル取得のメイン実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | api.server.ts | `frontend/internal-packages/github/src/api.server.ts` | getFileContent関数の実装 |

**主要処理フロー**:
1. **54-59行目**: 関数シグネチャと戻り値型定義
2. **60-61行目**: repositoryFullNameの分割
3. **63-65行目**: 分割失敗時のエラーハンドリング
4. **67行目**: Octokitクライアント生成
5. **69-76行目**: repos.getContent API呼び出し
6. **77-82行目**: レスポンス検証とBase64デコード
7. **84-85行目**: 非ファイル時の警告
8. **87-99行目**: 404エラーの検出と静音処理
9. **100-103行目**: その他エラーのログ出力

#### Step 3: downloadFileContent関数を理解する

Raw URLからのファイルダウンロードを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | api.server.ts | `frontend/internal-packages/github/src/api.server.ts` | downloadFileContent関数の実装 |

**主要処理フロー**:
- **246-250行目**: 関数シグネチャ（url、timeoutMs、maxBytes）
- **252-260行目**: URL検証（raw.githubusercontent.com制限）
- **262-280行目**: AbortControllerによるタイムアウト制御
- **295-302行目**: Content-Lengthによるサイズ制限チェック
- **305行目**: response.text()でテキストとして取得

#### Step 4: getFolderContents関数を理解する

フォルダ内のファイル一覧取得を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | api.server.ts | `frontend/internal-packages/github/src/api.server.ts` | getFolderContents関数の実装 |

**主要処理フロー**:
- **216-233行目**: 内部関数getFolderContentsAsync
- **223行目**: 認証なしOctokitの使用（公開リポジトリ用）
- **232行目**: 配列でなければ単一要素配列に変換
- **235-241行目**: ResultAsync型でラップしてエクスポート

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

```
呼び出し元（スキーマ画面等）
    │
    └─ getFileContent(repositoryFullName, filePath, ref, installationId)
           │
           ├─ repositoryFullName.split('/')
           │      └─ [owner, repo] 分割
           │
           ├─ createOctokit(installationId)
           │      └─ GitHub App認証
           │
           └─ octokit.repos.getContent({ owner, repo, path, ref })
                  │
                  └─ Buffer.from(data.content, 'base64').toString('utf-8')

[公開リポジトリ用]
downloadFileContent(url, timeoutMs?, maxBytes?)
    │
    ├─ new URL(url)  ← URL解析
    │      └─ hostname検証 (raw.githubusercontent.com)
    │
    ├─ AbortController + setTimeout
    │      └─ タイムアウト制御
    │
    └─ fetch(url, { signal })
           │
           ├─ Content-Length チェック
           │
           └─ response.text()
```

### データフロー図

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

                     ┌─────────────────────────┐
repositoryFullName ─▶│   split('/')           │
                     │   → [owner, repo]      │
                     └──────────┬──────────────┘
                                │
                     ┌──────────▼──────────────┐
installationId ─────▶│   createOctokit        │
                     │   (GitHub App認証)      │
                     └──────────┬──────────────┘
                                │
                     ┌──────────▼──────────────┐
filePath, ref ──────▶│   repos.getContent     │
                     │                         │
                     └──────────┬──────────────┘
                                │
                     ┌──────────▼──────────────┐
Base64 content ─────▶│   Buffer.from(...,     │──────────▶ { content, sha }
                     │   'base64').toString()  │
                     └─────────────────────────┘

[ダウンロードフロー]

                     ┌─────────────────────────┐
URL ────────────────▶│   hostname検証         │
                     │   (raw.githubusercontent│
                     │    .comのみ)            │
                     └──────────┬──────────────┘
                                │
                     ┌──────────▼──────────────┐
                     │   fetch + timeout      │──────────▶ Result<string, Error>
                     │   + size check          │
                     └─────────────────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| api.server.ts | `frontend/internal-packages/github/src/api.server.ts` | ソース | ファイル取得のメイン実装 |
| types.ts | `frontend/internal-packages/github/src/types.ts` | ソース | GitHubContentItem型定義 |
| index.ts | `frontend/internal-packages/github/src/index.ts` | ソース | パッケージエントリーポイント |
| api.server.test.ts | `frontend/internal-packages/github/src/api.server.test.ts` | テスト | ファイル取得のテスト |
