# 機能設計書 12-メンバーCSVインポート・エクスポート

## 概要

本ドキュメントは、GhostにおけるメンバーCSVインポート/エクスポート機能の設計仕様を記載する。本機能は、メンバーデータの一括登録（インポート）および一括出力（エクスポート）を行うための機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：
サイト運営者が他のプラットフォームからの移行時や、大量のメンバーを一括登録する必要がある場合に、CSVファイルを使用した効率的なデータ入出力が求められる。また、メンバーデータのバックアップやデータ分析のためのエクスポート機能も必要とされる。Stripeと連携したサブスクリプション情報のインポートにも対応する。

**機能の利用シーン**：
- 他プラットフォームからGhostへのメンバー移行時
- 既存のメーリングリストからメンバーを一括登録する場合
- メンバーデータをバックアップまたは分析用に出力する場合
- 有料会員情報をStripe顧客IDと紐付けてインポートする場合

**主要な処理内容**：
1. CSVファイルのアップロードと解析
2. ヘッダーマッピングによるカラム対応付け
3. 行ごとのメンバー作成または更新処理
4. Stripeカスタマー連携処理（stripe_customer_id指定時）
5. ラベルの自動付与（インポートラベル）
6. 処理結果のメール通知（大量インポート時）
7. エクスポート用CSVファイルの生成

**関連システム・外部連携**：
- Stripe API（顧客ID検証、サブスクリプション連携）
- メール送信サービス（インポート完了通知）
- ラベル管理機能（インポート時の自動ラベル付与）

**権限による制御**：
- インポート: メンバー追加権限が必要
- エクスポート: メンバー閲覧権限が必要

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 17 | メンバー一覧画面 | 参照画面 | 一括エクスポート機能 |
| 19 | メンバーインポート画面 | 主画面 | CSVファイルからのメンバー一括インポート |

## 機能種別

データ連携 / バッチ処理

## 入力仕様

### 入力パラメータ（インポート）

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| file | File | Yes | CSVファイル | ファイル形式チェック |
| mapping | Object | No | カラムマッピング設定 | - |
| labels | Array | No | 全メンバーに適用するラベル | - |

### CSVヘッダーマッピング（デフォルト）

| 内部フィールド | CSVカラム名 | 説明 |
|---------------|------------|------|
| email | email | メールアドレス（必須） |
| name | name | 名前 |
| note | note | メモ（2000文字以内） |
| subscribed_to_emails | subscribed | メール購読フラグ |
| created_at | created_at | 作成日時 |
| complimentary_plan | complimentary_plan | 無料提供フラグ |
| stripe_customer_id | stripe_customer_id | StripeカスタマーID |
| labels | labels | ラベル（カンマ区切り） |
| import_tier | import_tier | インポート先Tier名 |

### 入力データソース

- 管理画面からのファイルアップロード
- バックグラウンドジョブによる処理

## 出力仕様

### 出力データ（エクスポート）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | string | メンバーID |
| email | string | メールアドレス |
| name | string | 名前 |
| note | string | メモ |
| subscribed_to_emails | boolean | メール購読状態 |
| created_at | datetime | 作成日時 |
| status | string | メンバーステータス |
| labels | string | ラベル（カンマ区切り） |
| tiers | string | 所属Tier |

### 出力先

- CSVファイル（ダウンロード）
- メール通知（大量インポート時の結果通知）

## 処理フロー

### 処理シーケンス（インポート）

```
1. CSVファイル受信
   └─ ファイルをストレージに保存
2. 準備フェーズ（prepare）
   └─ ヘッダーマッピング適用、出力ファイル生成
3. 実行判定
   └─ 500件以下かつStripeデータなし → 同期実行
   └─ それ以外 → バックグラウンドジョブ
4. 実行フェーズ（perform）
   └─ 行ごとにトランザクション処理
5. 結果通知
   └─ バックグラウンド時はメール送信
```

### フローチャート

```mermaid
flowchart TD
    A[CSVアップロード] --> B[prepare: ヘッダーマッピング]
    B --> C{500件以下 AND Stripeデータなし?}
    C -->|Yes| D[同期実行: perform]
    C -->|No| E[バックグラウンドジョブ登録]
    E --> F[非同期実行: perform]
    D --> G[結果返却]
    F --> H[完了メール送信]
    G --> I[終了]
    H --> I
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-12-01 | 既存メンバー更新 | 同一メールアドレスが存在する場合、更新処理を行う | インポート時 |
| BR-12-02 | ラベル追記 | 既存メンバーの場合、ラベルは上書きではなく追記 | インポート時 |
| BR-12-03 | 購読状態保持 | 既存メンバーが購読解除済みの場合、再購読させない | インポート時 |
| BR-12-04 | インポートラベル | 各インポートに日時付きラベルを自動生成 | インポート時 |
| BR-12-05 | 未来日付補正 | created_atが未来の場合、現在日時に補正 | インポート時 |
| BR-12-06 | Stripe自動検索 | stripe_customer_id が 'auto' の場合、メールでStripe顧客を検索 | インポート時 |
| BR-12-07 | Tier指定制限 | 無料メンバーにTierを指定することは不可 | インポート時 |

### 計算ロジック

- バッチサイズ判定: 500件以下かつStripeデータなし → 同期処理

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

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| メンバー作成 | members | INSERT | 新規メンバー追加 |
| メンバー更新 | members | UPDATE | 既存メンバー情報更新 |
| ラベル関連付け | members_labels | INSERT | メンバーへのラベル付与 |
| ラベル作成 | labels | INSERT | 新規ラベル（インポートラベル含む） |
| Stripe連携 | members_stripe_customers | INSERT | Stripe顧客との紐付け |
| Tier付与 | members_products | INSERT | Tier（Product）の付与 |

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

#### members

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT/UPDATE | email | CSVの値 | 必須、一意識別子 |
| INSERT/UPDATE | name | CSVの値（空の場合は保持） | 既存値保持ロジック |
| INSERT/UPDATE | note | CSVの値（空の場合は保持） | 2000文字制限 |
| INSERT/UPDATE | subscribed | CSVの値 | true/false/0/1 |
| INSERT | created_at | CSVの値または現在日時 | 未来日付は補正 |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| DataImportError | 400 | メールアドレスが空 | 有効なメールを入力 |
| DataImportError | 400 | noteが2000文字超過 | 文字数を削減 |
| DataImportError | 400 | 無効なTier名指定 | 正しいTier名を指定 |
| DataImportError | 400 | 無料メンバーにTier指定 | complimentary_planまたはstripe_customer_idを設定 |
| ValidationError | 400 | Stripeカスタマーが見つからない | 正しいStripeアカウントを確認 |

### リトライ仕様

- 行単位でトランザクション管理
- 失敗行はエラーCSVに記録
- 他の行の処理は継続

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

- 行単位でトランザクション実行
- 失敗時はロールバックし、エラー情報を蓄積
- doNotRejectOnRollback: false で明示的なエラー検出

## パフォーマンス要件

- 500件以下: 同期処理（即時レスポンス）
- 500件超またはStripeデータあり: バックグラウンドジョブ
- エクスポート: ストリーミングレスポンス対応

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

- 認証必須: Admin API アクセストークン
- ファイルパスの安全な管理
- Stripe顧客データへの適切なアクセス制御
- エラーCSVにセンシティブ情報を含めない

## 備考

- 大量インポート後は email verification trigger がチェックされる
- インポート結果はメールで通知（エラーCSV添付）

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | members-csv-importer.js | `ghost/core/core/server/services/members/importer/members-csv-importer.js` | CSVインポーターの全体構造、ヘッダーマッピング定義 |

**読解のコツ**:
- **19-29行目**: DEFAULT_CSV_HEADER_MAPPING でデフォルトのCSVヘッダー対応を確認
- クラス構造でprepare/perform/processの責務分担を把握

**主要処理フロー**:
- **80-115行目**: prepare() - CSVの解析とマッピング
- **122-296行目**: perform() - メンバー作成/更新のメインロジック
- **382-443行目**: process() - 同期/非同期の振り分け判定

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | members.js | `ghost/core/core/server/api/endpoints/members.js` | importCSV/exportCSVエンドポイント |

**主要処理フロー**:
- **370-399行目**: exportCSV - ストリーミングレスポンス
- **402-445行目**: importCSV - インポート処理呼び出し

#### Step 3: エクスポート処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | query.js | `ghost/core/core/server/services/members/exporter/query.js` | エクスポートSQLクエリ |

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

```
members.js (API Endpoint)
    │
    ├─ importCSV()
    │      └─ membersService.processImport()
    │             └─ MembersCSVImporter.process()
    │                    ├─ prepare() - CSV解析
    │                    └─ perform() - 行単位処理
    │                           ├─ membersRepository.get()
    │                           ├─ membersRepository.create()
    │                           ├─ membersRepository.update()
    │                           └─ membersRepository.linkStripeCustomer()
    │
    └─ exportCSV()
           └─ membersService.export()
                  └─ exporter/query.js
```

### データフロー図

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

CSVファイル            members-csv-importer.js
   │                         │
   ├─ prepare() ────────────▶│
   │   (ヘッダーマッピング)     │
   │                         │
   └─ perform() ────────────▶│──────▶ members テーブル
       (行単位処理)            │       members_labels
                              │       members_products
                              │
                              └─────▶ 結果メール (大量時)
                                      エラーCSV

[エクスポート]

exporter/query.js ────▶ CSVストリーム ────▶ ダウンロード
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| members-csv-importer.js | `ghost/core/core/server/services/members/importer/members-csv-importer.js` | ソース | CSVインポート処理 |
| members.js | `ghost/core/core/server/api/endpoints/members.js` | ソース | APIエンドポイント |
| query.js | `ghost/core/core/server/services/members/exporter/query.js` | ソース | エクスポートクエリ |
| labels.js | `ghost/core/core/server/services/members/importer/labels.js` | ソース | インポート用ラベル処理 |
| email-template.js | `ghost/core/core/server/services/members/importer/email-template.js` | ソース | インポート結果メールテンプレート |
| members-csv-importer-stripe-utils.js | `ghost/core/core/server/services/members/importer/members-csv-importer-stripe-utils.js` | ソース | Stripe連携ユーティリティ |
