# 機能設計書 26-Uploadthing ストレージ

## 概要

本ドキュメントは、PayloadCMSにおけるUploadthingストレージアダプターの機能設計を記述する。このアダプターはファイルアップロード機能と連携し、ファイルをUploadthingサービスに保存・配信する機能を提供する。Uploadthingは開発者向けのファイルアップロードサービスで、シンプルなAPIと優れた開発者体験を提供する。

### 本機能の処理概要

Uploadthingストレージアダプターは、PayloadCMSのアップロードコレクションで生成されたファイルをUploadthingサービスに保存し、取得・削除を行うための包括的なストレージソリューションを提供する。Uploadthingのキーベース管理により、ファイルの一意な識別と効率的なアクセスを実現する。

**業務上の目的・背景**：Uploadthingは「Just works」の哲学で設計されたファイルアップロードサービスで、複雑なクラウドストレージ設定なしに迅速にファイルアップロード機能を実装できる。特に小規模から中規模のプロジェクトで、インフラ設定に時間をかけたくない場合に最適である。また、Vercel等のサーバーレス環境での動作にも対応している。

**機能の利用シーン**：
- 迅速なファイルアップロード機能の実装が必要なプロジェクト
- インフラ設定を最小限に抑えたいケース
- Vercel等のサーバーレス環境でのファイル永続化
- 開発者体験を重視するチーム

**主要な処理内容**：
1. ファイルアップロード処理：UTApi.uploadFilesによるアップロード
2. ファイル削除処理：UTApi.deleteFilesによる削除
3. ファイル配信処理：署名付きURLによるファイル取得
4. クライアント直接アップロード：Uploadthing Client SDKによる直接アップロード
5. キー管理：_keyフィールドによるファイル一意識別

**関連システム・外部連携**：
- Uploadthing API（uploadthing/server）
- Uploadthing Client SDK（uploadthing/client）
- Uploadthing CDN（utfs.io）

**権限による制御**：
- ACL設定（private/public-read）によるアクセス制御
- クライアントアップロード時のアクセス制御関数によるカスタム認可
- 署名付きURLによるプライベートファイルアクセス

## 関連画面

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

## 機能種別

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

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| options | UTApiOptions & {acl?: ACL} | Yes | Uploadthing API設定（token等） | Uploadthing仕様に準拠 |
| collections | Record<string, CollectionOptions\|true> | Yes | 適用対象コレクション設定 | 存在するコレクションスラッグ |
| options.acl | 'private'\|'public-read' | No | アクセス制御（デフォルト: 'public-read'） | - |
| clientUploads | {access?, routerInputConfig?}\|boolean | No | クライアント直接アップロード設定 | - |
| enabled | boolean | No | プラグイン有効化（デフォルト: true） | - |

### 入力データソース

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

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| url | string | Uploadthingファイルの公開URL |
| filename | string | Uploadthingが返すファイル名 |
| _key | string | Uploadthingファイルキー |
| filesize | number | ファイルサイズ（バイト） |
| mimeType | string | MIMEタイプ |

### 出力先

- Uploadthingサービス（オブジェクトストレージ）
- HTTPレスポンス（ファイル配信、URLレスポンス）

## 処理フロー

### 処理シーケンス

```
1. プラグイン初期化
   └─ UTApiインスタンス生成
   └─ _keyフィールドをコレクションに追加
   └─ コレクション設定へのアダプター注入
   └─ クライアントアップロード用ルート設定

2. ファイルアップロード（サーバー経由）
   └─ ファイルデータ受信
   └─ 既存キーがあれば古いファイルを削除
   └─ UTFile作成、uploadFiles API
   └─ _keyフィールドに保存
   └─ filenameをUploadthing返却値で更新

3. ファイルアップロード（クライアント直接）
   └─ genUploaderでアップローダー生成
   └─ uploadFiles APIで直接アップロード
   └─ 返却キーをcontextに保存

4. ファイル配信
   └─ ファイル取得リクエスト
   └─ docから_keyを抽出（なければDB検索）
   └─ getSignedURLで署名付きURL取得
   └─ fetchでコンテンツ取得
   └─ ストリーミングレスポンス

5. ファイル削除
   └─ 削除リクエスト
   └─ docから_keyを抽出
   └─ deleteFiles API呼び出し
```

### フローチャート

```mermaid
flowchart TD
    A[ファイルアップロード開始] --> B{clientUploadContext?}
    B -->|Yes| C[古いファイル削除]
    B -->|No| D[UTFile作成]
    C --> D
    D --> E[uploadFiles API]
    E --> F{サイズ情報あり?}
    F -->|Yes| G[sizes._keyに保存]
    F -->|No| H[_keyに保存]
    G --> I[filenameを更新]
    H --> I
    I --> J[終了]

    K[ファイル取得リクエスト] --> L{clientUploadContext?}
    L -->|Yes| M[contextからkey取得]
    L -->|No| N{docあり?}
    N -->|Yes| O[docから_key取得]
    N -->|No| P[DB検索]
    P --> O
    M --> Q[getSignedURL]
    O --> Q
    Q --> R[fetchでコンテンツ取得]
    R --> S[ストリーミングレスポンス]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-001 | デフォルトACL | public-readをデフォルトとする | acl未指定時 |
| BR-002 | キー管理 | _keyフィールドでUploadthingキーを管理 | 全アップロード時 |
| BR-003 | サイズ対応 | imageSizesがある場合、sizes.{name}._keyにも保存 | リサイズ画像あり時 |
| BR-004 | クライアントアップロード最大サイズ | デフォルト512MB | clientUploads時 |
| BR-005 | クライアントアップロードファイル数 | 1ファイルのみ | clientUploads時 |
| BR-006 | デフォルトアクセス制御 | クライアントアップロード時、認証済みユーザーのみ許可 | accessが未指定時 |

### 計算ロジック

- 公開URL: `https://utfs.io/f/${prefix}/${key}`
- _key抽出: `doc.filename === filename ? doc._key : doc.sizes.{name}._key`

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| アップロード | uploads（コレクション名に依存） | UPDATE | _key、filenameフィールド更新 |
| 配信 | uploads（コレクション名に依存） | SELECT | filenameで検索して_keyを取得 |
| 削除 | uploads（コレクション名に依存） | - | Uploadthingファイルのみ削除（DBはPayloadコアが管理） |

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

#### uploadsコレクション（追加フィールド）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | _key | uploadFiles返却値のkey | Uploadthingファイルキー |
| UPDATE | filename | uploadFiles返却値のname | Uploadthingが返すファイル名 |
| UPDATE | sizes.{name}._key | uploadFiles返却値のkey | リサイズ画像のキー |
| SELECT | _key, sizes.{name}._key | filename一致条件 | ファイル配信時のキー取得 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| APIError | 400 Bad Request | uploadFilesでエラー発生 | エラーコード・メッセージを確認 |
| APIError | 400 Bad Request | deleteFilesでキー抽出失敗 | ドキュメントの_keyフィールドを確認 |
| 404 Not Found | Not Found | getSignedURLで存在しないキー | キーの存在を確認 |
| Forbidden | 403 Forbidden | クライアントアップロードのアクセス制御違反 | 認証状態の確認 |
| FiberFailure | Error | Uploadthing内部エラー | エラーのtoJSONで詳細確認 |

### リトライ仕様

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

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

Uploadthing操作はトランザクション非対応のため、PayloadのDB操作とは独立して実行される。アップロード失敗時、DBへの保存も行われないため一貫性は保たれる。clientUploadContext使用時は古いファイルの削除も実行される。

## パフォーマンス要件

- UTApiインスタンスはアダプター生成時に1回のみ作成
- ファイル配信は署名付きURLによるリダイレクトではなく、プロキシ経由のストリーミング
- Range Requestによる部分コンテンツ配信対応
- ETagによる条件付きリクエストで帯域節約

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

- Uploadthingトークンは環境変数で管理（コード内にハードコードしない）
- ACL設定（private/public-read）によるアクセス制御
- プライベートファイルは署名付きURLで配信
- SVGファイルには`Content-Security-Policy: script-src 'none'`を付与しXSS防止
- クライアントアップロードではアクセス制御関数でユーザー認証を確認
- _keyフィールドは管理画面から非表示（hidden: true）

## 備考

- Uploadthingはファイル名を変更する場合があるため、返却されたnameをfilenameに保存
- imageSizesを使用する場合、各サイズごとに別のキーが生成される
- routerInputConfigでUploadthingのファイル制限をカスタマイズ可能

---

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

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

### 推奨読解順序

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

Uploadthing Storageオプションの型定義と、追加フィールド（_key）を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | index.ts | `packages/storage-uploadthing/src/index.ts` | UploadthingStorageOptions型定義（24-67行目） |
| 1-2 | index.ts | `packages/storage-uploadthing/src/index.ts` | _keyフィールド定義（155-166行目） |

**読解のコツ**: Uploadthingはキーベースでファイルを管理するため、_keyフィールドが重要。このフィールドはhidden: trueで管理画面から非表示。

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

uploadthingStorage関数がプラグインのエントリーポイント。UTApiインスタンスを生成しアダプターを構成。

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

**主要処理フロー**:
1. **79-97行目**: クライアントアップロートの初期化
2. **104-106行目**: デフォルトACL（public-read）の設定
3. **108行目**: uploadthingInternal関数でアダプター生成
4. **111-128行目**: 各コレクションにアダプターを注入
5. **148-151行目**: cloudStoragePluginを呼び出し設定を返却

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

uploadthingInternal関数でUTApiインスタンスを生成し、アダプターを構成。

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

**主要処理フロー**:
- **155-166行目**: _keyフィールド定義
- **174行目**: UTApiインスタンス生成
- **176-184行目**: GeneratedAdapterオブジェクトの生成
  - fields: _keyフィールド追加
  - generateURL: 公開URL生成
  - handleDelete: 削除処理
  - handleUpload: アップロード処理
  - staticHandler: ファイル配信

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

_key抽出ロジック。ドキュメントまたはsizesから該当ファイルのキーを取得。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | utilities.ts | `packages/storage-uploadthing/src/utilities.ts` | getKeyFromFilename関数（4-24行目） |

**主要処理フロー**:
- **5-13行目**: ドキュメント直接のfilename/_keyチェック
- **14-23行目**: sizes内のfilename/_keyチェック

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

ファイルのUploadthingへのアップロード処理。既存ファイル削除、UTFile作成、キー保存。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | handleUpload.ts | `packages/storage-uploadthing/src/handleUpload.ts` | getHandleUpload関数（14-67行目） |

**主要処理フロー**:
- **17-25行目**: clientUploadContextがあれば古いファイルを削除
- **29-30行目**: Blob作成、uploadFiles API呼び出し
- **37-46行目**: サイズ情報またはメインドキュメントに_keyを保存

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

Uploadthingファイルの削除処理。_keyを抽出してdeleteFiles API呼び出し。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 6-1 | handleDelete.ts | `packages/storage-uploadthing/src/handleDelete.ts` | getHandleDelete関数（12-35行目） |

**主要処理フロー**:
- **14行目**: getKeyFromFilenameで_key抽出
- **16-21行目**: キー未発見時のエラー処理
- **24-26行目**: deleteFiles API呼び出し

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

静的ファイルハンドラー。署名付きURLによるファイル取得。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 7-1 | staticHandler.ts | `packages/storage-uploadthing/src/staticHandler.ts` | getHandler関数（13-157行目） |

**主要処理フロー**:
- **22-67行目**: キー取得（context、doc、DB検索の順）
- **73行目**: getSignedURLで署名付きURL取得
- **80-86行目**: HEADリクエストでファイルサイズ取得
- **88-96行目**: Range Requestの解析と検証
- **98-103行目**: Range対応fetchでコンテンツ取得
- **129-131行目**: SVGファイルへのCSPヘッダー付与
- **148-151行目**: ストリーミングレスポンス返却

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

Uploadthing Client SDKを使用したクライアントからの直接アップロード機能。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 8-1 | getClientUploadRoute.ts | `packages/storage-uploadthing/src/getClientUploadRoute.ts` | getClientUploadRoute関数（28-69行目） |
| 8-2 | UploadthingClientUploadHandler.ts | `packages/storage-uploadthing/src/client/UploadthingClientUploadHandler.ts` | UploadthingClientUploadHandler（6-24行目） |

**主要処理フロー**:
- **getClientUploadRoute.ts 34行目**: createUploadthingでファイルルーター生成
- **getClientUploadRoute.ts 37-45行目**: blobルーター設定（maxFileSize: 512MB、maxFileCount: 1）
- **getClientUploadRoute.ts 46-60行目**: middlewareでアクセス制御
- **UploadthingClientUploadHandler.ts 13-16行目**: genUploaderでアップローダー生成
- **UploadthingClientUploadHandler.ts 18-20行目**: uploadFilesでアップロード

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

```
uploadthingStorage (Plugin Entry)
    │
    ├─ initClientUploads()
    │      └─ getClientUploadRoute()
    │             ├─ createUploadthing()
    │             ├─ middleware (アクセス制御)
    │             └─ createRouteHandler()
    │
    └─ uploadthingInternal()
           │
           ├─ new UTApi()
           │
           ├─ generateURL
           │      └─ getKeyFromFilename()
           │
           ├─ getHandleDelete()
           │      ├─ getKeyFromFilename()
           │      └─ utApi.deleteFiles()
           │
           ├─ getHandleUpload()
           │      ├─ utApi.deleteFiles() [古いファイル]
           │      └─ utApi.uploadFiles()
           │
           └─ getHandler()
                  ├─ getKeyFromFilename() / DB検索
                  ├─ utApi.getSignedURL()
                  └─ fetch() [ストリーミング]
```

### データフロー図

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

ファイルデータ ────────▶ getHandleUpload() ────────────▶ Uploadthing
(multipart/form-data)     │
                          ├─ UTFile作成
                          ├─ uploadFiles()
                          └─ _keyをdataに保存

ファイル取得リクエスト ──▶ getHandler() ────────────────▶ HTTPレスポンス
(GET /api/.../file.jpg)   │                                (ストリーミング)
                          ├─ getKeyFromFilename() / DB検索
                          ├─ getSignedURL()
                          └─ fetch() → Response

クライアントアップロード ──▶ getClientUploadRoute() ────▶ Uploadthing
(Uploadthing Client SDK)  │
                          ├─ middleware (認証)
                          └─ createRouteHandler()
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.ts | `packages/storage-uploadthing/src/index.ts` | ソース | プラグインエントリーポイント、UploadthingStorageOptions型定義 |
| utilities.ts | `packages/storage-uploadthing/src/utilities.ts` | ソース | getKeyFromFilename関数（_key抽出） |
| handleUpload.ts | `packages/storage-uploadthing/src/handleUpload.ts` | ソース | ファイルアップロード処理（UTApi.uploadFiles） |
| handleDelete.ts | `packages/storage-uploadthing/src/handleDelete.ts` | ソース | ファイル削除処理（UTApi.deleteFiles） |
| staticHandler.ts | `packages/storage-uploadthing/src/staticHandler.ts` | ソース | 静的ファイル配信（署名付きURL経由） |
| generateURL.ts | `packages/storage-uploadthing/src/generateURL.ts` | ソース | 公開URL生成（utfs.io） |
| getClientUploadRoute.ts | `packages/storage-uploadthing/src/getClientUploadRoute.ts` | ソース | クライアントアップロード用サーバールート |
| UploadthingClientUploadHandler.ts | `packages/storage-uploadthing/src/client/UploadthingClientUploadHandler.ts` | ソース | クライアント側直接アップロード処理 |
| client.ts | `packages/storage-uploadthing/src/exports/client.ts` | ソース | クライアントエクスポート |
