# 機能設計書 42-クラウドストレージ

## 概要

本ドキュメントは、Payload CMSにおける汎用クラウドストレージ統合プラグイン（plugin-cloud-storage）の設計を記載する。本プラグインは、アップロードされたファイルをローカルストレージの代わりにクラウドストレージ（S3、Azure Blob、GCS等）にオフロードするためのアダプターベースのソリューションを提供する。

### 本機能の処理概要

**業務上の目的・背景**：本番環境でのファイル管理において、スケーラビリティ、信頼性、CDN連携などの観点からクラウドストレージが求められる。本プラグインは、統一されたインターフェースで複数のクラウドストレージプロバイダーをサポートし、コレクション単位での柔軟な設定を可能にする。

**機能の利用シーン**：
- 大量のメディアファイルをS3やGCSで管理する場合
- CDN経由でファイルを配信する場合
- ローカルストレージの容量制限を回避する場合
- マルチリージョンでのファイル配信が必要な場合
- クライアントサイドから直接クラウドにアップロードする場合

**主要な処理内容**：
1. アップロードコレクションへのアダプター設定の適用
2. afterChangeフックでのファイルアップロード処理
3. afterDeleteフックでのファイル削除処理
4. カスタムstaticHandlerによるファイル配信
5. URL生成ロジックの差し替え
6. クライアントアップロードのサポート
7. 画像リサイズ時の各サイズのアップロード処理

**関連システム・外部連携**：
- Amazon S3 / S3互換ストレージ
- Azure Blob Storage
- Google Cloud Storage
- Vercel Blob
- Cloudflare R2
- Uploadthing

**権限による制御**：
- `disablePayloadAccessControl`オプションでPayloadのアクセス制御を無効化可能
- クライアントアップロード時は`clientUploads.access`で制御

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 10 | ドキュメント編集画面 | 主画面 | アップロードフィールドでのファイルアップロード |
| 9 | 一覧画面 | 参照画面 | アップロードファイルのサムネイル表示 |

## 機能種別

データ連携 / ファイル処理 / CRUD操作

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| enabled | boolean | No | プラグインの有効/無効 | デフォルト: true |
| alwaysInsertFields | boolean | No | 無効時もフィールドを挿入 | デフォルト: false |
| collections | object | Yes | コレクション別のアダプター設定 | UploadCollectionSlugをキーとする |
| collections[slug].adapter | Adapter | Yes | ストレージアダプター | 必須 |
| collections[slug].prefix | string | No | ファイルパスのプレフィックス | - |
| collections[slug].disableLocalStorage | boolean | No | ローカルストレージの無効化 | デフォルト: true |
| collections[slug].disablePayloadAccessControl | boolean | No | Payloadアクセス制御の無効化 | - |
| collections[slug].generateFileURL | function | No | カスタムURL生成関数 | - |

### 入力データソース

- Payload設定ファイルでのプラグイン設定
- リクエストのファイルデータ（req.file）
- 画像リサイズ結果（req.payloadUploadSizes）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| url | string | クラウドストレージ上のファイルURL |
| filename | string | ファイル名 |
| prefix | string | ファイルパスのプレフィックス |
| sizes | object | 画像の各サイズのURL情報 |

### 出力先

- クラウドストレージ（S3、Azure、GCS等）
- データベース（ファイルメタデータ）
- API（ファイルURL）

## 処理フロー

### 処理シーケンス

```
1. プラグイン初期化
   └─ enabled === false の場合
      └─ alwaysInsertFields === true ならフィールドのみ挿入
2. 各アップロードコレクションの設定
   └─ アダプターの初期化
3. コレクションフックの追加
   ├─ beforeChange: ファイルデータの保持
   ├─ afterChange: クラウドへのアップロード
   └─ afterDelete: クラウドからの削除
4. staticHandlerの設定
   └─ アダプター経由でのファイル配信
5. フィールドの追加
   └─ prefix、URLフィールドの追加
```

### フローチャート

```mermaid
flowchart TD
    A[ファイルアップロードリクエスト] --> B[Payloadの標準処理]
    B --> C[beforeChange: ファイルデータ保持]
    C --> D[ドキュメント保存]
    D --> E[afterChange: クラウドアップロード]
    E --> F{更新操作?}
    F -->|Yes| G[旧ファイル削除]
    G --> H[新ファイルアップロード]
    F -->|No| H
    H --> I{リサイズ画像あり?}
    I -->|Yes| J[各サイズをアップロード]
    I -->|No| K[メタデータ更新]
    J --> K
    K --> L[完了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | ローカル無効化 | デフォルトでローカルストレージを無効化 | disableLocalStorage未指定時 |
| BR-02 | 更新時削除 | ファイル更新時は旧ファイルを削除してから新ファイルをアップロード | operation === 'update' |
| BR-03 | リサイズ対応 | 画像リサイズ結果も個別にアップロード | 画像コレクションでリサイズ設定あり |
| BR-04 | アクセス制御 | disablePayloadAccessControl=falseの場合はPayloadのアクセス制御を適用 | デフォルト動作 |
| BR-05 | skipSafeFetch | ローカルホストからのリクエストは許可リストに追加 | 開発環境（NODE_ENV !== 'production'） |

### 計算ロジック

特になし

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| ファイルアップロード | アップロードコレクション | INSERT/UPDATE | ファイルメタデータを保存 |
| メタデータ更新 | アップロードコレクション | UPDATE | アップロード後のURL等を更新 |
| ファイル削除 | アップロードコレクション | DELETE | ドキュメント削除に伴う処理 |

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

#### アップロードコレクション

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | url | クラウドストレージのURL | afterChangeで更新 |
| UPDATE | prefix | ファイルパスのプレフィックス | 設定値を保存 |
| UPDATE | sizes.*.url | 各サイズのURL | リサイズ画像がある場合 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | Error | アップロード失敗 | エラーをログ出力してスロー |
| - | Warning | メタデータ更新失敗 | ログ出力のみ（ファイルはアップロード済み） |
| - | Error | アダプター未設定 | コレクション設定を確認 |

### リトライ仕様

プラグインレベルでのリトライ機能は未実装。各アダプター（S3等）側でのリトライ設定を利用。

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

- ファイルアップロードとメタデータ更新は別トランザクション
- メタデータ更新失敗時もファイルはクラウドにアップロード済み
- `skipCloudStorage`コンテキストフラグで無限ループを防止

## パフォーマンス要件

- 大容量ファイルはストリーミング処理
- 複数サイズの画像は並列アップロード（Promise.all）
- skipSafeFetch設定で開発環境でのパフォーマンス最適化

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

- disablePayloadAccessControl=trueの場合、クラウドストレージのパブリックアクセス設定に依存
- クライアントアップロード時は専用のアクセス制御関数で検証
- 署名付きURL等のセキュリティ機能は各アダプター側で実装

## 備考

- 本プラグインはアダプターパターンを採用し、各クラウドプロバイダー用の別パッケージと組み合わせて使用
- storage-s3、storage-azure、storage-gcs等の専用パッケージが提供されている

---

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

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

### 推奨読解順序

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

アダプターインターフェースと設定型を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/plugin-cloud-storage/src/types.ts` | Adapter型、GeneratedAdapter型、PluginOptions型 |

**読解のコツ**: `GeneratedAdapter`がアダプターの実装すべきインターフェースを定義している。`handleUpload`、`handleDelete`、`generateURL`、`staticHandler`が主要なメソッド。

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

プラグインのメインロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | plugin.ts | `packages/plugin-cloud-storage/src/plugin.ts` | cloudStoragePlugin関数の全体フロー |

**主要処理フロー**:
1. **21-63行目**: enabled=falseの場合の処理（alwaysInsertFields対応）
2. **69-81行目**: 各コレクションのアダプター初期化
3. **82-88行目**: フィールドの取得
4. **90-107行目**: staticHandlerの設定
5. **150-178行目**: コレクション設定の返却（フック、upload設定含む）
6. **183-188行目**: onInit処理

#### Step 3: フックを理解する

ファイル処理の中核となるフックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | afterChange.ts | `packages/plugin-cloud-storage/src/hooks/afterChange.ts` | アップロード処理 |
| 3-2 | afterDelete.ts | `packages/plugin-cloud-storage/src/hooks/afterDelete.ts` | 削除処理 |
| 3-3 | beforeChange.ts | `packages/plugin-cloud-storage/src/hooks/beforeChange.ts` | URL生成 |
| 3-4 | preserveFileData.ts | `packages/plugin-cloud-storage/src/hooks/preserveFileData.ts` | ファイルデータ保持 |

**主要処理フロー**:
- **afterChange.ts 16-18行目**: skipCloudStorageフラグのチェック
- **afterChange.ts 21行目**: getIncomingFilesでファイル取得
- **afterChange.ts 26-47行目**: 更新時の旧ファイル削除
- **afterChange.ts 50-60行目**: 新ファイルのアップロード
- **afterChange.ts 72-91行目**: メタデータ更新

#### Step 4: ユーティリティを理解する

ヘルパー関数を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | getIncomingFiles.ts | `packages/plugin-cloud-storage/src/utilities/getIncomingFiles.ts` | リクエストからファイル取得 |
| 4-2 | getFilePrefix.ts | `packages/plugin-cloud-storage/src/utilities/getFilePrefix.ts` | プレフィックス取得 |

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

```
cloudStoragePlugin (plugin.ts)
    │
    ├─ getFields (fields/getFields.ts)
    │      └─ URLフィールド、prefixフィールドの生成
    │
    ├─ getAfterChangeHook (hooks/afterChange.ts)
    │      ├─ getIncomingFiles (utilities/getIncomingFiles.ts)
    │      ├─ adapter.handleDelete
    │      └─ adapter.handleUpload
    │
    ├─ getAfterDeleteHook (hooks/afterDelete.ts)
    │      └─ adapter.handleDelete
    │
    ├─ getPreserveFileDataHook (hooks/preserveFileData.ts)
    │      └─ ファイルデータの保持
    │
    └─ adapter.staticHandler
           └─ ファイル配信
```

### データフロー図

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

アップロード ─────────────▶ beforeChange ─────────────▶ ファイルデータ保持
リクエスト                      │
                               ▼
                         ドキュメント保存
                               │
                               ▼
                         afterChange
                               │
                    ┌──────────┴──────────┐
                    ▼                     ▼
              旧ファイル削除        新ファイルアップロード
              (更新時のみ)               │
                                        ▼
                                   メタデータ更新
                                        │
                                        ▼
                                   レスポンス返却
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| plugin.ts | `packages/plugin-cloud-storage/src/plugin.ts` | ソース | プラグインのエントリーポイント |
| types.ts | `packages/plugin-cloud-storage/src/types.ts` | ソース | 型定義 |
| afterChange.ts | `packages/plugin-cloud-storage/src/hooks/afterChange.ts` | ソース | アップロードフック |
| afterDelete.ts | `packages/plugin-cloud-storage/src/hooks/afterDelete.ts` | ソース | 削除フック |
| beforeChange.ts | `packages/plugin-cloud-storage/src/hooks/beforeChange.ts` | ソース | URL生成フック |
| preserveFileData.ts | `packages/plugin-cloud-storage/src/hooks/preserveFileData.ts` | ソース | ファイルデータ保持 |
| getFields.ts | `packages/plugin-cloud-storage/src/fields/getFields.ts` | ソース | フィールド生成 |
| getIncomingFiles.ts | `packages/plugin-cloud-storage/src/utilities/getIncomingFiles.ts` | ソース | ファイル取得 |
| getFilePrefix.ts | `packages/plugin-cloud-storage/src/utilities/getFilePrefix.ts` | ソース | プレフィックス取得 |
| initClientUploads.ts | `packages/plugin-cloud-storage/src/utilities/initClientUploads.ts` | ソース | クライアントアップロード初期化 |
