# 機能設計書 23-GCS ストレージ

## 概要

本ドキュメントは、PayloadCMSにおけるGoogle Cloud Storage（GCS）アダプターの機能設計を記述する。このアダプターはファイルアップロード機能と連携し、ファイルをGoogle Cloud Storageバケットに保存・配信する機能を提供する。

### 本機能の処理概要

GCSストレージアダプターは、PayloadCMSのアップロードコレクションで生成されたファイルをGoogle Cloud Storageバケットに保存し、取得・削除を行うための包括的なストレージソリューションを提供する。

**業務上の目的・背景**：Google Cloudエコシステムを採用している企業では、GCSが自然なストレージ選択肢となる。Google Cloud CDN、Cloud Functions、BigQueryなどの他のGoogleサービスとの統合が容易であり、Googleの強力なインフラストラクチャによる高い可用性と耐久性を活用できる。また、Multi-Regional/Regional/Nearline/Coldlineなどのストレージクラスを用途に応じて選択可能である。

**機能の利用シーン**：
- Google Cloud Platform上でホストされているアプリケーション
- Firebase Hostingとの連携が必要なプロジェクト
- Google Cloud CDNによるグローバルコンテンツ配信
- BigQueryを使用したメディア分析が必要なシステム

**主要な処理内容**：
1. ファイルアップロード処理：file.saveによる直接アップロード
2. ファイル削除処理：ignoreNotFoundオプション付きの削除
3. ファイル配信処理：Range Requestに対応した静的ファイルハンドラー
4. 署名付きURL生成：V4署名によるクライアント直接アップロード
5. ACL制御：Public/Privateのアクセス制御

**関連システム・外部連携**：
- Google Cloud Storage API（@google-cloud/storage）
- Google Cloud CDN（コンテンツ配信）
- Google Cloud IAM（認証・認可）

**権限による制御**：
- ACL設定（Private/Public）によるオブジェクトレベルの公開設定
- V4署名付きURLによる一時的な書き込み権限付与
- クライアントアップロード時のアクセス制御関数によるカスタム認可

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | ドキュメント編集画面 | 主画面 | ファイルアップロード時のGCS保存処理 |
| 9 | 一覧画面 | 参照画面 | GCSに保存されたファイルのURL表示 |

## 機能種別

外部サービス連携（クラウドストレージ）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| bucket | string | Yes | GCSバケット名 | GCS命名規則準拠 |
| options | StorageOptions | Yes | GCSクライアント設定（credentials、projectId等） | GCS SDK仕様に準拠 |
| collections | Record<string, CollectionOptions\|true> | Yes | 適用対象コレクション設定 | 存在するコレクションスラッグ |
| acl | 'Private'\|'Public' | No | アクセス制御リスト | - |
| clientUploads | ClientUploadsConfig | No | クライアント直接アップロード設定 | - |
| clientCacheKey | string | No | GCSクライアントキャッシュキー | - |
| enabled | boolean | No | プラグイン有効化（デフォルト: true） | - |

### 入力データソース

- Payloadコレクション設定（payload.config.ts）
- ファイルアップロードリクエスト（multipart/form-data）
- クライアント直接アップロードリクエスト（application/json）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| url | string | GCSオブジェクトの公開URL |
| filename | string | 保存されたファイル名 |
| prefix | string | GCSキーのプレフィックス |
| filesize | number | ファイルサイズ（バイト） |
| mimeType | string | MIMEタイプ |

### 出力先

- Google Cloud Storageバケット（オブジェクトストレージ）
- HTTPレスポンス（ファイル配信、URLレスポンス）

## 処理フロー

### 処理シーケンス

```
1. プラグイン初期化
   └─ Storageクライアントの生成・キャッシュ
   └─ コレクション設定へのアダプター注入
   └─ クライアントアップロード用ルート設定

2. ファイルアップロード（サーバー経由）
   └─ ファイルデータ受信
   └─ file.save APIでバッファ保存
   └─ ACL設定（makePublic/makePrivate）
   └─ ドキュメントデータ返却

3. ファイルアップロード（クライアント直接）
   └─ V4署名付きURL生成リクエスト
   └─ アクセス権限チェック
   └─ 書き込み権限付き署名URL生成
   └─ クライアントがGCSに直接PUT

4. ファイル配信
   └─ ファイル取得リクエスト
   └─ メタデータ取得
   └─ Range Requestヘッダー解析
   └─ createReadStreamでオブジェクト取得
   └─ ストリーミングレスポンス

5. ファイル削除
   └─ 削除リクエスト
   └─ file.delete API呼び出し（ignoreNotFound: true）
```

### フローチャート

```mermaid
flowchart TD
    A[ファイルアップロード開始] --> B[file.save API]
    B --> C{ACL設定あり?}
    C -->|Yes & Public| D[makePublic]
    C -->|Yes & Private| E[makePrivate]
    C -->|No| F[ACLスキップ]
    D --> G[アップロード完了]
    E --> G
    F --> G
    G --> H[ドキュメント更新]
    H --> I[終了]

    J[ファイル取得リクエスト] --> K[getMetadata]
    K --> L{Range Request?}
    L -->|Yes| M[部分createReadStream]
    L -->|No| N[全体createReadStream]
    M --> O[ReadableStream構築]
    N --> O
    O --> P[ストリーミングレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | ACL自動設定 | アップロード後にmakePublic/makePrivateを自動実行 | aclオプションが指定されている場合 |
| BR-002 | 署名付きURL有効期限 | 5時間（18000秒） | クライアントアップロード時 |
| BR-003 | 署名バージョン | V4署名を使用 | クライアントアップロード時 |
| BR-004 | デフォルトアクセス制御 | クライアントアップロード時、認証済みユーザーのみ許可 | accessが未指定時 |
| BR-005 | 削除時の存在確認 | ignoreNotFound: trueで存在しない場合もエラーにしない | 削除時 |

### 計算ロジック

- GCSオブジェクトキー: `${prefix}/${filename}`
- 公開URL: `file.publicUrl()` で自動生成（https://storage.googleapis.com/${bucket}/${key}形式）

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| アップロード | uploads（コレクション名に依存） | UPDATE | prefix、url等のフィールド更新 |
| 削除 | uploads（コレクション名に依存） | - | GCSオブジェクトのみ削除（DBはPayloadコアが管理） |

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

#### uploadsコレクション

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | prefix | クライアントアップロード時にクライアントから渡される値 | GCSキープレフィックス |
| UPDATE | url | generateURL関数で生成されたURL | 公開アクセス用URL |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ApiError 404 | 404 Not Found | 存在しないGCSオブジェクトへのアクセス | ファイルパスの確認 |
| AuthenticationError | 401 Unauthorized | サービスアカウント認証失敗 | 認証情報の確認 |
| APIError | 400 Bad Request | クライアントアップロードの不正リクエスト | リクエスト形式の確認 |
| Forbidden | 403 Forbidden | クライアントアップロードのアクセス制御違反 | 認証状態の確認 |

### リトライ仕様

Google Cloud SDKのデフォルトリトライポリシーを使用。

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

GCS操作はトランザクション非対応のため、PayloadのDB操作とは独立して実行される。アップロード失敗時、DBへの保存も行われないため一貫性は保たれる。削除時はignoreNotFoundオプションにより冪等性を確保。

## パフォーマンス要件

- Storageクライアントインスタンスはキャッシュされ再利用される
- ファイル配信はcreateReadStreamでストリーミング処理
- Range Requestに対応し、部分コンテンツの効率的な配信が可能

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

- GCSサービスアカウント認証情報は環境変数または認証ファイルで管理
- V4署名付きURLは書き込み権限のみを付与し、有効期限を5時間に制限
- SVGファイルには`Content-Security-Policy: script-src 'none'`を付与しXSS防止
- クライアントアップロードではアクセス制御関数でユーザー認証を確認
- ファイル名はsanitizeFilename関数でサニタイズ
- CORSは別途GCSバケット設定で設定が必要

## 備考

- file.publicUrl()はバケットがpublic accessを許可している場合のみ動作
- V4署名はGoogleの推奨署名バージョンで、最大7日間の有効期限をサポート
- Google Cloud CDNと組み合わせる場合は別途CDNエンドポイントの設定が必要

---

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

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

### 推奨読解順序

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

GCS Storageオプションの型定義と、クラウドストレージプラグインの共通インターフェースを理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/storage-gcs/src/index.ts` | GcsStorageOptions型定義（21-67行目）でプラグインオプションを把握 |

**読解のコツ**: optionsパラメータはGoogle Cloud StorageのStorageOptions型を直接使用。acl設定は'Private'/'Public'の2値。

#### Step 2: エントリーポイントを理解する

gcsStorage関数がプラグインのエントリーポイント。Payloadの設定を受け取り、GCSアダプターを注入した設定を返す。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.ts | `packages/storage-gcs/src/index.ts` | gcsStorage関数（73-148行目）でプラグイン初期化フローを理解 |

**主要処理フロー**:
1. **76行目**: クライアントキャッシュキーの決定
2. **78-85行目**: Storageクライアントの遅延初期化とキャッシュ
3. **87行目**: gcsStorageInternal関数でアダプター生成
4. **91-106行目**: クライアント直接アップロードの初期化
5. **113-124行目**: 各コレクションにアダプターを注入
6. **144-147行目**: cloudStoragePluginを呼び出し設定を返却

#### Step 3: アダプター生成を理解する

gcsStorageInternal関数でコレクションごとのアダプターを生成する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | index.ts | `packages/storage-gcs/src/index.ts` | gcsStorageInternal関数（150-170行目）でアダプター構造を把握 |

**主要処理フロー**:
- **155-168行目**: GeneratedAdapterオブジェクトの生成
  - generateURL: 公開URL生成
  - handleDelete: 削除処理
  - handleUpload: アップロード処理
  - staticHandler: ファイル配信

#### Step 4: アップロード処理を理解する

ファイルのGCSへのアップロード処理。file.save後にACL設定を適用。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | handleUpload.ts | `packages/storage-gcs/src/handleUpload.ts` | ファイルアップロード処理全体（15-37行目） |

**主要処理フロー**:
- **22行目**: GCSオブジェクトキーの構築
- **24-29行目**: file.save APIでバッファ保存（contentType設定付き）
- **31-33行目**: ACL設定がある場合、makePublic/makePrivateを実行

#### Step 5: 削除処理を理解する

GCSオブジェクトの削除処理。ignoreNotFoundで冪等性を確保。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | handleDelete.ts | `packages/storage-gcs/src/handleDelete.ts` | 削除処理全体（11-17行目） |

**主要処理フロー**:
- **13-15行目**: file.delete API呼び出し（ignoreNotFound: trueで存在しない場合もOK）

#### Step 6: ファイル配信を理解する

静的ファイルハンドラー。Range Request、ETag、ストリーミングに対応。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | staticHandler.ts | `packages/storage-gcs/src/staticHandler.ts` | getHandler関数（15-100行目） |

**主要処理フロー**:
- **21行目**: getMetadataでファイルメタデータ取得
- **24-33行目**: Range Requestの解析と検証
- **49-51行目**: SVGファイルへのCSPヘッダー付与
- **61-66行目**: ETagによる304レスポンス判定
- **69-86行目**: createReadStreamからWeb ReadableStreamへの変換
- **88-91行目**: ストリーミングレスポンス返却

#### Step 7: URL生成を理解する

GCSのpublicUrl()メソッドを使用した公開URL生成。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 7-1 | generateURL.ts | `packages/storage-gcs/src/generateURL.ts` | getGenerateURL関数（11-17行目） |

**主要処理フロー**:
- **14-16行目**: file.publicUrl()で公開URLを取得し、decodeURIComponentでデコード

#### Step 8: クライアント直接アップロードを理解する

V4署名付きURLを使用したクライアントからの直接アップロード機能。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 8-1 | generateSignedURL.ts | `packages/storage-gcs/src/generateSignedURL.ts` | サーバー側署名URL生成（21-64行目） |
| 8-2 | GcsClientUploadHandler.ts | `packages/storage-gcs/src/client/GcsClientUploadHandler.ts` | クライアント側アップロード処理（5-34行目） |

**主要処理フロー**:
- **generateSignedURL.ts 45行目**: アクセス権限チェック
- **generateSignedURL.ts 49-50行目**: ファイル名のサニタイズとキー構築
- **generateSignedURL.ts 52-60行目**: V4署名付きURL生成（write権限、5時間有効）
- **GcsClientUploadHandler.ts 24-28行目**: クライアントからGCSへ直接PUT

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

```
gcsStorage (Plugin Entry)
    │
    ├─ getStorageClient()
    │      └─ new Storage() [キャッシュ]
    │
    ├─ initClientUploads()
    │      └─ getGenerateSignedURLHandler()
    │             └─ file.getSignedUrl() [V4署名URL生成]
    │
    └─ gcsStorageInternal()
           │
           ├─ getGenerateURL()
           │      └─ file.publicUrl()
           │
           ├─ getHandleDelete()
           │      └─ file.delete({ ignoreNotFound: true })
           │
           ├─ getHandleUpload()
           │      ├─ file.save()
           │      └─ file.makePublic() / file.makePrivate()
           │
           └─ getHandler()
                  ├─ file.getMetadata()
                  └─ file.createReadStream()
```

### データフロー図

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

ファイルデータ ────────▶ getHandleUpload() ────────────▶ GCSバケット
(multipart/form-data)     │
                          ├─ file.save()
                          └─ makePublic/makePrivate (optional)

ファイル取得リクエスト ──▶ getHandler() ────────────────▶ HTTPレスポンス
(GET /api/.../file.jpg)   │                                (ストリーミング)
                          ├─ getMetadata()
                          └─ createReadStream() → ReadableStream

クライアント署名リクエスト ▶ getGenerateSignedURLHandler() ▶ V4署名URL
(POST JSON)               │
                          └─ file.getSignedUrl({ version: 'v4' })
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/storage-gcs/src/index.ts` | ソース | プラグインエントリーポイント、GcsStorageOptions型定義 |
| handleUpload.ts | `packages/storage-gcs/src/handleUpload.ts` | ソース | ファイルアップロード処理（file.save + ACL設定） |
| handleDelete.ts | `packages/storage-gcs/src/handleDelete.ts` | ソース | ファイル削除処理（ignoreNotFound対応） |
| staticHandler.ts | `packages/storage-gcs/src/staticHandler.ts` | ソース | 静的ファイル配信（Range Request対応） |
| generateURL.ts | `packages/storage-gcs/src/generateURL.ts` | ソース | 公開URL生成（file.publicUrl使用） |
| generateSignedURL.ts | `packages/storage-gcs/src/generateSignedURL.ts` | ソース | クライアントアップロード用V4署名URL生成 |
| GcsClientUploadHandler.ts | `packages/storage-gcs/src/client/GcsClientUploadHandler.ts` | ソース | クライアント側直接アップロード処理 |
| client.ts | `packages/storage-gcs/src/exports/client.ts` | ソース | クライアントエクスポート |
