# 通知設計書 20-uploadFailed

## 概要

本ドキュメントは、Etherpadにおけるファイルアップロード失敗通知「uploadFailed」の設計仕様を定義する。この通知は、ファイルのアップロード処理が失敗した際に表示されるエラーメッセージである。

### 本通知の処理概要

この通知はファイルアップロード処理が失敗したことをユーザーに伝え、再試行を促すためのアプリ内表示である。

**業務上の目的・背景**：ファイルインポート機能において、アップロード処理は最初のステップである。ネットワーク障害、サーバーエラー、不正なファイル形式など、様々な理由でアップロードが失敗する可能性がある。この通知により、ユーザーはアップロードに問題があることを認識し、再試行や別のファイルの選択などの適切な対処を取ることができる。

**通知の送信タイミング**：サーバー側の`doImport`関数内で、以下の条件でImportError('uploadFailed')がスローされる：
1. Formidable.parseでのファイル受信エラー（biggerThanMaxFileSize以外）
2. フォームにfileフィールドがない場合
3. 未知のファイル拡張子でallowUnknownFileEnds設定がfalseの場合
4. 非ASCIIファイルのチェックに失敗した場合

**通知の受信者**：インポート操作を実行したユーザー本人のみ。

**通知内容の概要**：「Import failed:」というプレフィックスに続いて、「The upload failed, please try again」というメッセージが表示される。

**期待されるアクション**：ユーザーはファイルを確認し、再度アップロードを試みる。ファイル形式が問題の場合は、対応している形式のファイルを選択する。

## 通知種別

アプリ内通知（エラーメッセージ表示）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期（HTTP POSTレスポンス） |
| 優先度 | 高 |
| リトライ | 無 |

### 送信先決定ロジック

インポート操作を実行したクライアントでのみローカルに処理される。サーバーからのエラーレスポンス（code: 1, message: 'uploadFailed'）に基づいて表示が決定される。

## 通知テンプレート

### エラーメッセージ表示

| 項目 | 内容 |
|-----|------|
| 表示要素 | #importmessagefail |
| プレフィックス | pad.impexp.importfailed（「Import failed:」） |
| エラーメッセージ | pad.impexp.uploadFailed |
| 表示方法 | fadeIn |

### 本文テンプレート

```javascript
// サーバー側エラー発生箇所1: Formidableエラー
try {
  [fields, files] = await form.parse(req);
} catch (err) {
  logger.warn(`Import failed due to form error: ${err.stack || err}`);
  if (err.code === Formidable.formidableErrors.biggerThanMaxFileSize) {
    throw new ImportError('maxFileSize');
  }
  throw new ImportError('uploadFailed');
}

// サーバー側エラー発生箇所2: ファイルなし
if (!files.file) {
  logger.warn('Import failed because form had no file');
  throw new ImportError('uploadFailed');
}

// サーバー側エラー発生箇所3: 未知のファイル拡張子
if (fileEndingUnknown && settings.allowUnknownFileEnds !== true) {
  logger.warn(`Not allowing unknown file type to be imported: ${fileEnding}`);
  throw new ImportError('uploadFailed');
}

// サーバー側エラー発生箇所4: 非ASCIIファイル
if (!isAscii) {
  logger.warn('Attempt to import non-ASCII file');
  throw new ImportError('uploadFailed');
}
```

### 添付ファイル

なし

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| pad.impexp.importfailed | エラープレフィックス「Import failed」 | src/locales/*.json | Yes |
| pad.impexp.uploadFailed | アップロード失敗メッセージ | src/locales/*.json | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| フォームエラー | Formidable.parse例外 | maxFileSize以外のエラー | ファイル受信失敗 |
| ファイル不在 | files.file未定義 | フォームにfileフィールドがない | リクエスト不正 |
| 形式不許可 | 未知の拡張子 | allowUnknownFileEnds === false | セキュリティ制限 |
| 文字コードエラー | 非ASCIIファイル | useConverter === false | テキスト変換エラー |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| 正常アップロード | ファイルが正常に受信・処理された場合 |
| maxFileSizeエラー | ファイルサイズ超過の場合は別のエラー（maxFileSize） |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ファイルアップロード] --> B[Formidable.parse]
    B -->|エラー| C{maxFileSizeエラー?}
    C -->|Yes| D[ImportError:maxFileSize]
    C -->|No| E[ImportError:uploadFailed]
    B -->|成功| F{files.file存在?}
    F -->|No| G[ImportError:uploadFailed]
    F -->|Yes| H{拡張子チェック}
    H -->|未知 & 不許可| I[ImportError:uploadFailed]
    H -->|OK| J{ASCIIチェック}
    J -->|失敗| K[ImportError:uploadFailed]
    J -->|成功| L[インポート処理続行]
    E --> M[エラーレスポンス]
    G --> M
    I --> M
    K --> M
    M --> N[importErrorMessage:uploadFailed]
```

## データベース参照・更新仕様

### 参照テーブル一覧

なし（アップロードエラー時はDBアクセス前にエラー返却）

### 更新テーブル一覧

なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| フォーム解析エラー | ネットワーク障害、不正なリクエスト | 再試行 |
| ファイル未選択 | ユーザーがファイルを選択していない | ファイルを選択して再試行 |
| 未知のファイル形式 | .c, .javaなど対応外の拡張子 | 対応形式（.txt, .html等）に変換 |
| 非ASCIIエラー | バイナリ要素を含むテキスト | 別の形式で保存し直す |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（ユーザーが手動で再試行） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし

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

- 未知のファイル形式はデフォルトで拒否される（allowUnknownFileEnds: false）
- エラー詳細はサーバーログにのみ記録
- ファイル拡張子のホワイトリスト: .txt, .doc, .docx, .pdf, .odt, .html, .htm, .etherpad, .rtf
- クライアントにはファイル形式の詳細は伝えない（セキュリティのため）

## 備考

- settings.allowUnknownFileEnds = trueの場合、未知の拡張子は.txtとして処理される
- 非ASCIIチェックはuseConverter === falseの場合のみ実行
- クライアント側のAJAXタイムアウト（25秒）を超えると別のエラーになる可能性がある
- Formidableライブラリでファイル受信処理を行っている

---

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

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

### 推奨読解順序

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

まず、Formidableの設定構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ImportHandler.ts | `src/node/handler/ImportHandler.ts` | Formidableインスタンス作成（91-95行目） |

**読解のコツ**: keepExtensions, uploadDir, maxFileSizeオプションを確認する。

#### Step 2: エラー発生箇所を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ImportHandler.ts | `src/node/handler/ImportHandler.ts` | form.parseエラーハンドリング（100-108行目） |
| 2-2 | ImportHandler.ts | `src/node/handler/ImportHandler.ts` | files.fileチェック（109-114行目） |
| 2-3 | ImportHandler.ts | `src/node/handler/ImportHandler.ts` | 未知のファイル形式チェック（126-136行目） |
| 2-4 | ImportHandler.ts | `src/node/handler/ImportHandler.ts` | 非ASCIIチェック（180-191行目） |

**主要処理フロー**:
1. **101行目**: `[fields, files] = await form.parse(req)`
2. **104行目**: biggerThanMaxFileSizeチェック
3. **107行目**: その他のエラーでuploadFailed
4. **110-111行目**: files.fileの存在チェック
5. **133-134行目**: 未知拡張子 + allowUnknownFileEnds === falseでuploadFailed
6. **188-189行目**: 非ASCIIでuploadFailed

#### Step 3: クライアント側表示を確認

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | pad_impexp.ts | `src/static/js/pad_impexp.ts` | importErrorMessage関数（86行目でuploadFailedがknown配列に含まれる） |
| 3-2 | en.json | `src/locales/en.json` | pad.impexp.uploadFailedメッセージ（205行目） |

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

```
ImportHandler.doImport(req, res, padId, authorId)
    │
    ├─ form.parse(req)
    │      │
    │      └─ [エラー]
    │             │
    │             ├─ [biggerThanMaxFileSize] → ImportError('maxFileSize')
    │             └─ [その他] → ImportError('uploadFailed')
    │
    ├─ [files.file存在チェック]
    │      │
    │      └─ [なし] → ImportError('uploadFailed')
    │
    ├─ [ファイル拡張子チェック]
    │      │
    │      └─ [未知 && !allowUnknownFileEnds] → ImportError('uploadFailed')
    │
    ├─ [非ASCIIチェック（変換なし時）]
    │      │
    │      └─ [非ASCII] → ImportError('uploadFailed')
    │
    └─ [エラーレスポンス]
           │
           └─ res.json({code: 1, message: 'uploadFailed', ...})
                  │
                  ▼
           [クライアント側]
                  │
                  └─ importErrorMessage('uploadFailed')
                         │
                         └─ html10n.get('pad.impexp.uploadFailed')
                                │
                                └─ "The upload failed, please try again"
```

### データフロー図

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

ファイル選択 ───▶ form.parse ───▶ [エラー?]
                                        │
                              ┌─────────┴─────────┐
                              │                   │
                           [なし]              [あり]
                              │                   │
                              ▼                   ▼
                    files.file存在?    ImportError判定
                              │                   │
                    ┌─────────┴─────────┐         │
                    │                   │         │
                 [あり]              [なし]       │
                    │                   │         │
                    ▼                   ▼         │
             拡張子チェック    uploadFailed ◀─────┘
                    │                   │
          ┌─────────┴─────────┐         │
          │                   │         │
        [OK]              [NG]          │
          │                   │         │
          ▼                   ▼         │
    ASCIIチェック    uploadFailed ◀─────┘
          │                   │
     [OK] │ [NG]              │
          │    └──▶ uploadFailed
          ▼
    インポート続行
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ImportHandler.ts | `src/node/handler/ImportHandler.ts` | ソース | インポート処理、uploadFailedエラー生成 |
| pad_impexp.ts | `src/static/js/pad_impexp.ts` | ソース | クライアント側エラー表示 |
| en.json | `src/locales/en.json` | 設定 | エラーメッセージローカライズ |
| Settings.ts | `src/node/utils/Settings.ts` | 設定 | importMaxFileSize, allowUnknownFileEnds設定 |
| formidable | `node_modules/formidable` | 外部ライブラリ | マルチパートフォーム解析 |
