Skip to content

oharato/world-flags-learning

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

国旗学習アプリ

ゲーム感覚で楽しみながら世界の国旗を暗記できるWebアプリケーションです。

✨ 主な機能

  • クイズモード: 国旗を見て国名を選ぶ、または国名を見て国旗を選ぶ2種類のクイズ形式で、スコアを競います。

    • キーボードショートカット対応: Ctrl+Enter でクイズ開始、矢印キーで選択、Enterで回答送信
    • 難易度設定: 簡単(4択)、普通(6択)、難しい(8択)
    • 出題範囲設定: 全世界、アフリカ、アジア、ヨーロッパ、北アメリカ、南アメリカ、オセアニア
    • 画像プリロード: 次の問題の画像を事前読み込みし、スムーズな問題移行を実現
  • 学習モード: フラッシュカード形式で、国旗、国名、首都、大陸、地図、概要、国旗の由来をじっくり学習できます。

    • キーボードショートカット: 矢印キーで前後移動、スペースキーで表裏切り替え
    • 長文はマウスホバー/タッチでポップアップ表示
    • 画像の最適化: 即座に読み込まれる高速表示
  • ランキング機能: クイズで獲得したスコアを地域別・期間別・形式別で競い合えます。

    • 地域別ランキング: 全世界、アフリカ、アジア、ヨーロッパ、北アメリカ、南アメリカ、オセアニア
    • 期間別ランキング: 日次ランキング(当日のスコア)、全期間トップ5
    • 形式別ランキング: 国旗→国名、国名→国旗で別々のランキング
    • トップ3にはメダル表示(🥇🥈🥉)
    • ニックネームの自動保存(localStorage)
  • 多言語対応: 日本語・英語の切り替えに対応

🛠️ 使用技術

フロントエンド

アーキテクチャ

  • 再利用可能コンポーネント: DRY原則に基づいた設計
    • 共通UIコンポーネント: LanguageSelector, LoadingSpinner, ErrorMessage, AppButton
    • クイズ設定コンポーネント: RegionSelector, QuizFormatSelector
    • 学習用コンポーネント: FlagCard, CountryDetailCard
  • レスポンシブデザイン: モバイルファーストの設計
    • スマホとPCで最適化された異なるレイアウト
    • ランキング: PC版はテーブル、スマホ版はカード形式
    • フォーム: 画面サイズに応じた適切なサイズとスペーシング
    • バリデーションエラーの画面内表示
  • CI/CD: GitHub Actions による自動テスト・デプロイパイプライン

バックエンド

データソース

  • Wikipedia (国旗画像、地図画像、国旗の由来、概要など)
    • データ取得には wikijs を使用
  • Wikidata (首都情報: P36、大陸情報のフォールバック: P30)
    • Wikidata APIを直接使用し、言語別ラベルを取得
    • 大陸情報は主にWikipediaのカテゴリ(Category:〇〇の国 / Countries in 〇〇)から取得し、取得できない場合のみWikidataにフォールバック

🚀 ローカルでの実行方法

1. 依存関係のインストール

npm install

2. 国データの生成

Wikipedia と Wikidata から国旗や国の情報を取得し、アプリケーションが使用する public/countries.ja.jsonpublic/countries.en.json を生成します。 注意: この処理は外部APIにアクセスするため、時間がかかる場合があります(約30分〜1時間)。

  1. 国名/国旗ページマッピングの生成(任意)
npx tsx scripts/extract-flag-page-names.mts

... (truncated for brevity in this query; update uses full README content) ...

  1. 国データの生成と画像ダウンロード
npm run batch:create-data
各国について以下の情報を取得します:
- 国旗画像(`public/flags/` にダウンロード)
- 地図画像(`public/maps/` にダウンロード)
- 首都(Wikidata P36 から取得、言語別ラベル)
- 大陸(まずWikipediaのカテゴリHTMLから取得、失敗時はWikidata P30にフォールバック)
  - カテゴリパターン: 日本語版 `Category:〇〇の国`、英語版 `Countries in 〇〇`
  - 複数の大陸カテゴリに属する国(オーストラリア、パナマなど)に対応するため優先順位を設定
- 概要(Wikipedia の要約セクション)
- 国旗の由来(日本語版は「[国名]の国旗」ページ、英語版は日本語ページ内のリンクから自動検出)

処理は各国ごとに保存されるため、中断しても再実行時に続きから再開できます。

**特定の国のみ更新する場合**:
```bash
npm run batch:create-data "アメリカ"
```
国名の一部を指定することで、その国のみデータを更新できます。

**英語版の国旗の由来取得について**:
英語版では、日本語Wikipediaページ内の `https://en.wikipedia.org/wiki/Flag_of` で始まるリンクを自動的に検出して取得します。
これにより、国名のバリエーション("the", "Republic of" など)を気にすることなく、正確な英語版国旗ページにアクセスできます。

3. データベースのセットアップ

ローカルのD1データベースにテーブルを作成します。

npx wrangler d1 migrations apply world-flags-learning-db --local

初回実行時に確認を求められた場合は Y を入力してください。

このコマンドで以下のテーブルが作成されます:

  • ranking_daily: 日次ランキング(nickname, region, format, date ごとに UNIQUE)
  • ranking_all_time: 全期間ランキング(region と format ごとのベストスコア)

4. 開発サーバーの起動

フロントエンド(Vite)とバックエンド(Wrangler)の開発サーバーを同時に起動します。

npm run dev

起動後、ブラウザで http://localhost:5173 にアクセスしてください。

🧪 テスト

このプロジェクトでは Vitest を使用して包括的なテストを実行します。

テストの実行

# 全テストを実行(ウォッチモード)
npm test

# 全テストを1回だけ実行
npm run test:run

# UIでテストを実行
npm run test:ui

# カバレッジレポート付きでテストを実行
npm run test:coverage

コード品質チェック

このプロジェクトでは Biome を使用した高速なリンティングとフォーマットを行っています。

# コードのリントチェック
npm run lint

# リンターのエラーを自動修正
npm run lint:fix

# コードのフォーマット
npm run format

テストカバレッジ

113テスト、100%合格率

  • ストアのテスト (32テスト)

    • Countries Store: データ取得、言語切り替え、フィルタリング (14テスト)
    • Quiz Store: クイズ設定、問題生成、回答処理、スコア計算 (11テスト)
    • Ranking Store: ランキング取得、スコア送信 (7テスト)
  • コンポーネントのテスト (55テスト)

    • Home: 言語選択、ナビゲーション (8テスト)
    • QuizSetup: 入力バリデーション、XSS対策、localStorage連携 (14テスト)
    • Study: カード操作、地域フィルタリング、キーボードショートカット (22テスト)
    • LazyImage: 遅延読み込み、Intersection Observer API、画像ロード (11テスト)
  • ユーティリティのテスト (16テスト)

    • バリデーション: ニックネーム検証、セキュリティチェック、XSS対策
  • APIのテスト (10テスト)

    • Server: スコア計算、入力検証、セキュリティチェック

CI/CD統合

  • GitHub Actions による自動テスト実行(プルリクエスト時)
  • テスト成功後の自動デプロイ(main/master ブランチへのマージ時)

詳細は テスト仕様書 を参照してください。

🎨 開発ワークフロー

Pre-commit Hooks

このプロジェクトでは、コミット前に自動的にコード品質チェックとフォーマットを実行します。

  • 自動実行: git commit すると、ステージングされたファイルに対して自動的にBiomeが実行されます
  • 自動修正: リンターが検出した問題は自動的に修正されます
  • エラー防止: エラーがある場合、コミットはブロックされます

詳細は Pre-commit Hooks 設定ガイド を参照してください。

手動でのコード品質チェック

# リンターチェック
npm run lint

# リンターの自動修正
npm run lint:fix

# フォーマット
npm run format

🚀 本番環境へのデプロイ

このアプリケーションは Cloudflare Pages にデプロイされます。

デプロイ手順

1. Cloudflare にログイン

npx wrangler login

2. D1 データベースの作成(初回のみ)

npx wrangler d1 create world-flags-learning-db

出力された database_idwrangler.toml に設定してください。

3. マイグレーションの適用(初回のみ)

npx wrangler d1 migrations apply world-flags-learning-db --remote

4. デプロイ

npm run deploy

5. D1 バインディングの設定(初回のみ)

Cloudflare ダッシュボードで以下の設定を行います:

  1. https://dash.cloudflare.com/ にアクセス
  2. Workers & Pagesworld-flags-learning を選択
  3. Settings タブ → FunctionsD1 database bindings
  4. Add binding をクリック
    • Variable name: DB
    • D1 database: world-flags-learning-db を選択
  5. Save をクリック

デプロイコマンド

# 本番環境へデプロイ(ビルド + デプロイ)
npm run deploy

# プレビュー環境へデプロイ
npm run deploy:preview

デプロイ後の確認

デプロイが完了すると、以下のような URL が表示されます:

https://[deployment-id].world-flags-learning.pages.dev

この URL にアクセスしてアプリケーションの動作を確認してください。

GitHub Actions による自動テスト・デプロイ

CI/CDパイプライン

  1. プルリクエスト時: 自動的にテストを実行(マージ前の品質チェック)
  2. main/master ブランチへのマージ時: テスト成功後に自動デプロイ
  3. 国データの自動更新: 毎週日曜日に自動実行、または手動で実行可能

このワークフローにより、コードの品質を保ちながら安全にデプロイできます。

国データの自動更新

GitHub Actions により、Wikipedia と Wikidata から国データを定期的に更新できます:

自動実行: 毎週日曜日 0:00 (UTC) に自動的に実行

手動実行:

  1. GitHubリポジトリの Actions タブを開く
  2. Update Country Data ワークフローを選択
  3. Run workflow ボタンをクリック

このワークフローは以下を実行します:

  • npm run batch:create-data による国データの生成
  • public/ ディレクトリの変更を自動コミット(コミットメッセージ: "update by gha on {日時}")
  • 変更がある場合、自動的に Cloudflare Pages へデプロイ

初回設定(GitHub Secrets)

  1. Cloudflare API トークンを取得: https://dash.cloudflare.com/profile/api-tokens
  2. Cloudflare アカウント ID を取得: npx wrangler whoami
  3. GitHub リポジトリの SettingsSecrets and variablesActions
  4. 以下の2つの Secret を追加:
    • CLOUDFLARE_API_TOKEN: Cloudflare API トークン
    • CLOUDFLARE_ACCOUNT_ID: Cloudflare アカウント ID

詳細は GitHub Actions 自動デプロイ設定ガイド を参照してください。

� ディレクトリ構造

.
├── functions/       # Cloudflare Pages Functions (バックエンドAPI)
├── migrations/      # D1 データベースのマイグレーションファイル
├── public/          # 静的ファイル (国データJSON、ダウンロードされた国旗・地図画像など)
│   ├── flags/       # ダウンロードされた国旗画像
│   └── maps/        # ダウンロードされた地図画像
├── scripts/         # データ生成などのバッチスクリプト
└── src/
    ├── assets/      # 画像などのアセット
    ├── components/  # 共通コンポーネント
    ├── router/      # Vue Router の設定
    ├── server/      # (現在は未使用) Honoサーバーの旧格納場所
    ├── store/       # Pinia ストア (状態管理)
    ├── views/       # 各画面のVueコンポーネント
    ├── App.vue      # アプリケーションのルートコンポーネント
    ├── main.ts      # アプリケーションのエントリーポイント
    └── style.css    # グローバルスタイル

Cloudflare Pages 用のヘッダーとキャッシュ設定

背景

  • このリポジトリにあった public/_headers は Netlify 固有のフォーマットです。Cloudflare Pages では自動的に適用されないため、効果がありません。Netlify 用ファイルを削除し、Cloudflare 側で同等の設定を行ってください。

推奨する実装順(優先度順)

  1. オリジン(ビルド出力/S3 等)で Cache-Control やセキュリティヘッダーを付与する(最も簡単で確実)。
  2. Cloudflare ダッシュボードの Rules → Transform Rules / Response Header Modification でパスごとにヘッダーを追加・上書きする。
  3. Cloudflare Workers を使ってレスポンスを受け取りヘッダーを書き換える(細かい制御が必要な場合)。

既存の _headers に書かれていたルールと Cloudflare での対応例

  • /assets/*

    • Cache-Control: public, max-age=31536000, immutable
  • /flags/, /maps/

    • Cache-Control: public, max-age=2592000
  • /*.json

    • Cache-Control: public, max-age=86400
  • /sw.js

    • Cache-Control: public, max-age=3600
  • 全ページ(セキュリティヘッダー)

    • X-Frame-Options: DENY
    • X-Content-Type-Options: nosniff
    • Referrer-Policy: strict-origin-when-cross-origin
    • Permissions-Policy: geolocation=(), microphone=(), camera=()
    • X-XSS-Protection: 1; mode=block (注:多くのモダンブラウザでは非推奨であり、Content-Security-Policy(CSP)を併用することを推奨します)

Cloudflare ダッシュボードでの手順(簡易)

  1. https://dash.cloudflare.com/ にログインし、Pages サイトを選択します。
  2. Rules → Transform Rules(または Response Header Modification)を開きます。
  3. 条件(例: Path starts with /assets/)を作成し、レスポンスヘッダーを追加/上書きします。
  4. より柔軟な制御が必要なら Worker を作成してヘッダーを差し替える処理を実装します。

Cloudflare Worker の簡単なサンプル(参考)

下記はオリジンからのレスポンスに対してパスごとに Cache-Control を上書きし、全レスポンスにセキュリティヘッダーを追加する最小限の例です。必要に応じて README に載せるか、リポジトリに scripts/worker.js として追加してください。

addEventListener("fetch", event => { event.respondWith(handle(event.request)); });

async function handle(request) { const url = new URL(request.url); const res = await fetch(request); const headers = new Headers(res.headers);

if (url.pathname.startsWith("/assets/")) { headers.set("Cache-Control", "public, max-age=31536000, immutable"); } else if (url.pathname.startsWith("/flags/") || url.pathname.startsWith("/maps/")) { headers.set("Cache-Control", "public, max-age=2592000"); } else if (url.pathname.endsWith(".json")) { headers.set("Cache-Control", "public, max-age=86400"); } else if (url.pathname === "/sw.js") { headers.set("Cache-Control", "public, max-age=3600"); }

headers.set("X-Frame-Options", "DENY"); headers.set("X-Content-Type-Options", "nosniff"); headers.set("Referrer-Policy", "strict-origin-when-cross-origin"); headers.set("Permissions-Policy", "geolocation=(), microphone=(), camera=()"); headers.set("X-XSS-Protection", "1; mode=block");

return new Response(res.body, { status: res.status, statusText: res.statusText, headers }); }

注意点

  • Cloudflare のキャッシュ設定(Edge Cache TTL、Cache Rules)とオリジン側の Cache-Control の組合せに注意してください。オリジンで正しく設定するのが最も確実です。
  • X-XSS-Protection は多くの最新ブラウザで非推奨です。可能であれば CSP を導入してください。

About

国旗学習アプリ

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •