プロダクション化設計書
概要
MileTrek を「モックで動くプロトタイプ」から「実運用可能なサービス」へ昇格させるために必要な全設計を記述する。
1. 管理画面(Admin Panel)
結論: 必要。 以下の運用業務を非エンジニアでも行えるようにする。
1.1 管理画面の機能一覧
| 機能 | 優先度 | 説明 |
|---|---|---|
| キャンペーン管理 | 高 | ANA/JALキャンペーンの CRUD。公開/非公開切替、優先度設定 |
| マイルチャート管理 | 高 | ANA/JALが年1-2回更新するマイルチャートの編集。CSV一括更新 |
| シーズン定義管理 | 高 | L/R/H シーズン期間の年度更新 |
| レストランデータ管理 | 中 | 追加/編集/削除。食べログ/Google URL の手動キュレーション |
| ホテルデータ管理 | 中 | 追加/編集/削除。料金・画像の更新 |
| ユーザー管理 | 中 | ユーザー一覧、凍結/削除、パスワードリセット |
| 空港マスタ管理 | 低 | 新規空港追加(国際線拡充時) |
| 広告管理 | 中 | 広告枠の有効/無効、掲載位置設定 |
| アナリティクス | 中 | DAU/MAU、検索数、予約インテント数、人気目的地 |
| コンテンツモデレーション | 低 | ユーザー生成コンテンツ(レビュー等)が発生した場合 |
1.2 管理画面の技術方針
| 項目 | 方針 |
|---|---|
| フレームワーク | React + Vite(フロント共通化)or 軽量に AdminJS / React Admin |
| 認証 | JWT + admin ロール。users.role カラム追加(user/admin) |
| URL | /admin/* をExpress で配信。本番は Basic認証 + IP制限 |
| デザイン | シンプルなテーブル+フォーム。DESIGN.md のトークン流用 |
2. 認証・セキュリティ
2.1 現在の問題
| 問題 | リスク | 対応 |
|---|---|---|
✅ 対応済 (#71): JWT_SECRET を必須環境変数化。本番で未設定の場合は起動時にエラーで fail-fast。開発環境では ephemeral なランダム値を使用し警告を出力。 | ||
| パスワードリセットなし | ユーザーがロックアウトされる | メール送信 + リセットトークン |
| Rate limiting なし | ブルートフォース攻撃 | express-rate-limit 導入 |
CORS が * | CSRF 攻撃 | 本番ドメインのみ許可 |
| 入力バリデーション最小限 | インジェクション | express-validator 導入 |
| HTTPS 未強制 | 中間者攻撃 | Cloudflare で自動HTTPS |
| XSS 対策 | スクリプト注入 | helmet.js 導入 |
| SQLite WAL ロック | 同時書き込み制限 | 読み取り専用レプリカ or PostgreSQL 移行検討 |
2.2 ユーザーロール
users テーブル拡張:
role TEXT DEFAULT 'user' CHECK (role IN ('user', 'admin'))| ロール | 権限 |
|---|---|
| user | 通常のアプリ利用(検索、予約、グルメ、イベント管理) |
| admin | 上記 + 管理画面アクセス + データ CRUD |
2.3 JWT シークレット管理 (#77)
原則:秘密値はソースコード・リポジトリに一切含めない。
| 環境 | ソース | 備考 |
|---|---|---|
| ローカル開発 | ephemeral random (メモリのみ) | プロセス再起動で無効化 |
| 本番 (macOS/Linux VPS) | Keychain / pass / systemd EnvironmentFile | デプロイスクリプトで env 化 |
| CI/CD | GitHub Secrets | Actions run 時に inject |
トークン運用:
- アクセストークン: 15〜30分の短命
- リフレッシュトークン: 7日、DB管理
- 失効機能: Redis or
revoked_tokensテーブルで jti blacklist
3. 実データ統合
3.1 データソース別の統合方針
| データ | 現状 | 本番方針 | API/手段 | コスト |
|---|---|---|---|---|
| マイルチャート | シードデータ | 手動更新(年1-2回) + 管理画面CSVインポート | ANA/JAL公式PDFから転記 | 無料 |
| シーズン定義 (#78) | 2026分シード | 年次自動バッチ + 管理画面編集 | 公式PDF/ページパース(不可時はCSV手動) | 無料 |
| キャンペーン (#79) | モック10件 | 定期取得バッチ + 管理画面CRUD | ANA/JAL公式ページ スクレイプ/RSS | 無料 |
| 空席情報 | モック生成 | フェーズ判断要 ※下記参照 | - | - |
| ホテル | Unsplash画像+固定価格 | 楽天トラベルAPI | 楽天Web Service | 無料(レート制限あり) |
| レストラン | モック24件 | Google Places API + 手動キュレーション | Google Maps Platform | $17/1000リクエスト |
| レビュー(食べログ) | モック評価 | 食べログAPIは非公開 → 手動転記 or 検索リンクのみ | 手動 | 無料 |
| レビュー(Google) | モック評価 | Google Places API (rating, user_ratings_total) | Google Maps Platform | $17/1000リクエスト |
| 目的地画像 | Unsplash | Unsplash API or 固定画像CDN | Unsplash API (無料) | 無料 |
3.1.1 自動更新バッチ (#78, #79)
| 対象 | 実行頻度 | 手段 | 失敗時 |
|---|---|---|---|
| シーズン定義 | 年1回(1月1日) | GitHub Actions cron → scripts/update-seasons.mjs | Slack通知 + 管理画面で手動補正 |
| キャンペーン | 毎日 | GitHub Actions cron → scripts/fetch-campaigns.mjs | Sentry記録 + 前回データ維持 |
バッチは必ず「追加/更新」操作のみを行い、管理者が手動で編集した値は上書きしない(source='manual' 列で区別)。
3.1.2 キャンペーン考慮のマイル最適化 (#80)
検索・プラン作成時に DB の campaigns を参照し、最も少ないマイル消費となる出発日・路線・キャンペーン組み合わせを提案する。
検索結果.miles_required = mile_charts.miles_required
- applicable_campaigns.maxBy(discount_amount)
- (season 割引)UIでは「キャンペーン適用 -50% 🎉」バッジを表示し、元マイル数とキャンペーン名をホバー/タップで確認可能。
3.2 空席情報のフェーズ判断
ANA/JAL は空席情報の公開APIを提供していない。選択肢:
| 方式 | メリット | デメリット |
|---|---|---|
| A. モック維持(現状) | 開発コストゼロ | ユーザーの信頼性に欠ける |
| B. 公式サイトスクレイピング | リアルデータ | 利用規約違反リスク、構造変更で壊れる |
| C. 手動更新 | 確実 | 運用コスト大 |
| D. 空席非表示 + インテント起動のみ | リスクゼロ | UXが若干劣る |
| E. お得月のみ表示(推奨) | シーズン定義は公開情報 | 具体的な座席数は非表示 |
推奨: 方式 E — シーズン定義(L/R/H)に基づいて「お得な時期」を表示し、具体的な空席数は表示しない。予約ボタンから公式サイトへインテント起動。
4. インフラ・デプロイ
4.1 本番構成
[Cloudflare CDN/DNS]
│
▼
[Cloudflare Workers / Pages] ← 静的フロントエンド配信
│
▼ /api/*
[VPS / Docker Container]
├── Express Server (Node.js)
├── SQLite (data/miletrek.db)
└── Cron Jobs (キャンペーン更新チェック等)4.2 デプロイ方針
| 項目 | 方針 |
|---|---|
| コンテナ化 | Docker + docker-compose |
| フロントエンド | Cloudflare Pages(自動ビルド+CDN配信) |
| バックエンド | VPS (ConoHa / さくら / Vultr) or Fly.io |
| データベース | SQLite(小〜中規模)→ 大規模時 PostgreSQL 移行 |
| CI/CD | GitHub Actions(lint + test + build + deploy) |
| 監視 | UptimeRobot(死活)+ Sentry(エラー) |
| ログ | winston + ログローテーション |
| バックアップ | SQLite DB を毎日 S3/R2 にバックアップ |
| ドメイン | miletrek.jp or miletrek.app |
4.3 環境変数
env
NODE_ENV=production
JWT_SECRET=<Keychain管理>
PORT=3000
DB_PATH=/data/miletrek.db
RAKUTEN_API_KEY=<楽天Web Service>
GOOGLE_PLACES_API_KEY=<Google Maps Platform>
SENTRY_DSN=<Sentry>5. 収益化・広告
5.1 広告戦略
| 収益源 | 方式 | 想定収益 |
|---|---|---|
| Google AdSense | バナー広告(ダッシュボード下部、検索結果間) | CPM ¥100〜300 |
| アフィリエイト | 楽天トラベル/agoda/booking.com リンク経由の予約手数料 | 予約額の3-8% |
| ネイティブ広告 | グルメタブのスポンサードカード | 月額固定 or CPC |
| プレミアム機能 | 広告非表示、優先空席通知 | 月額 ¥300〜500 |
5.2 アフィリエイト連携
| サービス | プログラム | 報酬率 |
|---|---|---|
| 楽天トラベル | 楽天アフィリエイト | 予約額の1% |
| agoda | agoda パートナー | 予約額の5-7% |
| booking.com | Booking.com アフィリエイト | 予約額の25-40%(コミッション) |
| じゃらん | リクルートアフィリエイト | 予約額の1% |
ホテル予約リンクをアフィリエイトリンクに差し替えるだけで収益化可能。
5.3 広告枠の設計(DESIGN.md 準拠)
ダッシュボード
└── マイル残高の下: バナー広告 (320x100)
検索結果
└── 3件目と6件目の後: ネイティブ広告カード
グルメタブ
└── 先頭: スポンサードレストラン
イベント予算タブ
└── 合計の下: 旅行保険広告
旅のしおり(SNSシェア画面)
└── 広告なし(UX優先)6. UX 品質向上
6.1 必須対応
| 項目 | 現状 | 対応 |
|---|---|---|
| ローディング状態 | 一部のみ | 全API呼び出しにスケルトンUI |
| エラーハンドリング | 最小限 | ネットワークエラー、API失敗、再試行ボタン |
| オフライン対応 | なし | Service Worker でキャッシュ(PWA) |
| プッシュ通知 | なし | マイル有効期限アラート、お得なキャンペーン通知 |
| ダークモード | 設定UIあり/未実装 | DESIGN.md にダークパレット追加 + 実装 |
| アクセシビリティ | 未対応 | ARIA属性、キーボード操作、色コントラスト |
| 多言語対応 | 日本語のみ | i18n 基盤(react-intl)は Phase 4 |
| 画像最適化 | Unsplash直リンク | Cloudflare Image Resizing or imgproxy |
| コード分割 | なし(855KB単一チャンク) | React.lazy + dynamic import |
6.2 PWA 対応
json
// manifest.json
{
"name": "MileTrek",
"short_name": "MileTrek",
"start_url": "/",
"display": "standalone",
"background_color": "#FFFFFF",
"theme_color": "#0071E3",
"icons": [...]
}Service Worker キャッシュ戦略:
- マイルチャート → Cache First(月1更新)
- 空港マスタ → Cache First(年1更新)
- グルメ/ホテル → Network First + Stale While Revalidate
- 検索結果 → Network Only
7. テスト戦略
7.1 テスト種別
| 種別 | ツール | カバレッジ目標 |
|---|---|---|
| ユニットテスト | Vitest | API ルート: 80% |
| コンポーネントテスト | Testing Library | 主要コンポーネント: 70% |
| E2E テスト | Playwright | 主要フロー: 100% |
| APIテスト | supertest | 全エンドポイント: 100% |
| リンクヘルスチェック | link-health-check.mjs | 全DB URL: 100% |
7.2 CI パイプライン
yaml
# .github/workflows/ci.yml
on: [push, pull_request]
jobs:
test:
- npm install
- npm run lint
- npm run test
- npm run build
- node scripts/link-health-check.mjs
deploy:
needs: test
if: github.ref == 'refs/heads/main'
- docker build & push
- deploy to production8. 法務・コンプライアンス
| 項目 | 必要性 | 対応 |
|---|---|---|
| 利用規約 | 必須 | /terms ページ作成 |
| プライバシーポリシー | 必須 | /privacy ページ。個人情報保護法対応 |
| 特定商取引法表記 | 有料機能提供時 | /legal ページ |
| Cookie同意 | GDPR/改正電通法 | Cookie バナー |
| 免責事項 | 必須 | マイル情報・空席情報はANA/JAL公式を確認するよう明記 |
| 航空券仲介業 | 不要 | 直接販売しない(インテント起動のみ) |
| 旅行業法 | 不要 | 手配行為を行わない(情報提供のみ) |
9. SEO・マーケティング
| 項目 | 対応 |
|---|---|
| SSR/プリレンダリング | 主要ランディングページのみ静的生成 |
| Meta タグ | title, description, OGP画像 |
| サイトマップ | /sitemap.xml 自動生成 |
| 構造化データ | JSON-LD(FAQPage, HowTo) |
| SNSシェア | 旅のしおり画面の OGP画像自動生成 |
| ASO(アプリストア) | PWA としてホーム画面追加を促進 |
10. フェーズ計画
Phase 3: プロダクション化(推奨順序)
3-1. セキュリティ強化 ─────── JWT Keychain連携+短命化+失効 (#77), helmet, rate limiting
3-2. 管理画面 MVP ──────── キャンペーン/マイルチャート/シーズン/ユーザー管理 (#55)
3-3. 自動更新バッチ ─────── シーズン年次バッチ (#78) + キャンペーン日次バッチ (#79)
3-4. マイル最適化エンジン ── キャンペーン考慮の最適プラン提案 (#80)
3-5. 楽天トラベルAPI統合 ─── ホテル実データ取得 + アフィリエイト (#56)
3-6. Docker化 + CI/CD ──── GitHub Actions + 本番デプロイ (#57)
3-7. 法務ページ ──────── 利用規約/プライバシーポリシー (#58)
3-8. エラーハンドリング ──── 全画面のローディング/エラー/リトライ (#59)
3-9. Google Places API ─── レストラン実データ + レビュー (#60)
3-10. PWA化 ─────────── manifest + Service Worker + オフライン (#61)
3-11. テスト整備 ────── Vitest + Playwright E2E (#62)
3-12. 広告・アフィリエイト ── AdSense + 楽天/agoda/booking アフィリエイト (#63)Phase 4: グロース
4-1. プッシュ通知 ────── マイル有効期限 + お得キャンペーン
4-2. ダークモード ────── DESIGN.md ダークパレット
4-3. 国際線対応 ─────── 海外空港/路線データ追加
4-4. コード分割 ─────── React.lazy + dynamic import (バンドル最適化)
4-5. 多言語対応 ─────── react-intl (英語/中国語)
4-6. プレミアム機能 ──── 広告非表示、優先通知、高度な検索フィルタ