コンテンツにスキップ

user-web

Jester の管理画面(レース/ブログ管理ダッシュボード)です。 レースと記事の管理、過去ブログの取り込みと AI リライト、ブログ・カテゴリ・NGワードの管理、 および記事・ブログのプレビューをブラウザから操作できます。


技術スタック

項目 内容
ビルド Vite(bun run dev / bun run build。出力先は out/)
フレームワーク React 19 + react-router-dom v7(クライアントのみの SPA。サーバーランタイムは持たない)
スタイル Tailwind CSS v4(@tailwindcss/vite)
UI 部品 Radix UI
API 呼び出し ブラウザの fetch(src/lib/api.ts)
デザイン oddspark-static-pages から移植したコンポーネント・SCSS(src/oddspark/)

Next.js ではない

もとは Next.js アプリでしたが、Vite + React Router の SPA に移行しました。 サーバーコンポーネント・サーバーアクションはありません。

環境変数

変数名 説明
VITE_API_BASE_URL odds_poc_app(App Lambda)のベース URL
VITE_MEDIA_BASE_URL メディア配信用の CloudFront ドメイン

ディレクトリ構成

jester-user-web/
├── index.html
├── vite.config.ts
└── src/
    ├── main.tsx              # エントリ(BrowserRouter)
    ├── App.tsx               # ルート定義
    ├── pages/                # 1ルート = 1コンポーネント
    ├── components/           # 共有 UI・管理画面レイアウト
    ├── lib/
    │   ├── api.ts            # API 呼び出し(取得・更新)
    │   ├── types.ts          # API レスポンスの型
    │   ├── race-config.ts    # 競技種別とパスセグメントの対応
    │   └── race-result/      # レース結果の整形
    └── oddspark/             # oddspark から移植したデザイン資産

画面構成

管理画面(サイドバー付きレイアウト)

画面 パス 概要
競馬レース一覧 /horse-racings レースの一覧・記事生成の起点
オートレース一覧 /auto-racings 同上
競輪レース一覧 /bicycle-racings 同上
レース詳細 /:raceType/detail?id=... レース結果の確認・記事の生成・紐づく記事の一覧
レース記事詳細 /:raceType/detail/article?id=... 記事本文の確認・編集
生成失敗一覧 /articles/failed status=failed の記事一覧・追加指示つき再生成
ブログ一覧 /blogs 生成済みブログの一覧・絞り込み
ブログ詳細 /blogs/detail?id=... 本文・タイトル・カテゴリの編集、サムネイル生成、関連情報生成、削除
過去ブログ一覧 /old-blogs 過去ブログの一覧・取り込み
過去ブログ詳細 /old-blogs/detail?id=... カテゴリの更新・AI リライトの起点
カテゴリ一覧 /categories カテゴリの作成・更新・削除
NGワード一覧 /ng-words NGワードの作成・更新・削除

ルート(/)へアクセスすると、先頭の競技種別(競馬レース一覧)へリダイレクトします。

プレビュー(管理画面 UI なし・別タブで開く)

画面 パス 概要
レース結果ページ /articles/preview?raceId=... 記事サマリーを埋め込んだレース結果ページの実表示
ブログ記事ページ /blogs/preview?id=... ブログの実表示
リライト前ブログ /blogs/preview/original?id=... 同じ記事体裁のまま、本文だけリライト前に差し替えた比較用表示

プレビューは oddspark から移植したグローバル CSS(リセット含む)を取り込むため、 管理画面側のスタイルと分離するよう遅延読み込みしています。

詳細ページの ID はクエリ文字列

詳細ページは ID をパスパラメータではなくクエリ文字列(?id=...)で受け取ります。 競技種別3つ(horse-racings / auto-racings / bicycle-racings)は /:raceType ルートを共有します。


競技種別の扱い

URL のパスセグメントとバックエンドの競技種別は src/lib/race-config.ts で対応づけます。

kind パス racingType(API) 表示名 出走者番号
horse /horse-racings horse_racing 競馬レース horse_number
auto /auto-racings auto_racing オートレース car_number
bicycle /bicycle-racings bicycle_racing 競輪レース car_number

レース一覧は GET /v1/races?type={racingType} を呼びます。 race_id は競技をまたいで一意のため、詳細取得(GET /v1/races/{race_id})では種別を指定しません。


使用 API

レース・記事

操作 API
レース一覧 GET /v1/races?type=...
レース詳細 GET /v1/races/{race_id}
レースに紐づく記事 GET /v1/articles?race_id=... / GET /v1/races/{race_id}/articles/{article_id}
生成失敗記事の一覧 GET /v1/articles?status=failed
記事の AI 生成 POST /v1/articles/generation
生成失敗記事の再生成 POST /v1/articles/{article_id}/regeneration
記事本文の更新 PUT /v1/articles/{article_id}

ブログ・過去ブログ

操作 API
ブログ一覧 GET /v1/blogs(race_id / race_type / category_id / origin / status / name / id で絞り込み)
ブログ詳細 GET /v1/blogs/{blog_id}
ブログの更新(本文・タイトル・カテゴリ) PUT /v1/blogs/{blog_id}
ブログの削除 DELETE /v1/blogs/{blog_id}
サムネイル生成 POST /v1/blogs/{blog_id}/thumbnail_generate
関連情報の生成 POST /v1/blogs/{blog_id}/related_generate
過去ブログ一覧・詳細 GET /v1/old_blogs / GET /v1/old_blogs/{old_blog_id}
過去ブログの取り込み POST /v1/old_blogs
過去ブログのカテゴリ更新 PUT /v1/old_blogs/{old_blog_id}
AI リライトの開始 POST /v1/old_blogs/{old_blog_id}/rewrite

マスタ

操作 API
カテゴリ GET / POST / PUT / DELETE /v1/categories
NGワード GET / POST / PUT / DELETE /v1/ng_words

過去ブログの取り込み

過去ブログの取り込みは、scripts/scraping_blog.py(jester-backend)が出力した スクレイピング済み JSON ファイルを複数選択でアップロードする方式です。

画面側でファイルを1件ずつ読み込み、次のようにマッピングして POST /v1/old_blogs を呼びます。

JSON のキー リクエストのフィールド
title name
body_html body_html
race_type race_type
blog_type blog_type
category category
url source_url
published_at published_at(「2026年5月10日」形式・ISO 8601 の両方を UNIX ミリ秒へ変換)

title / body_html が無いファイルはスキップし、処理後に成功件数と失敗理由をまとめて表示します。


非同期処理の扱い

記事生成・ブログリライト・サムネイル生成は 202 Accepted ですぐ返り、 バックグラウンドで処理が進みます。画面は状態をポーリングせず、再読み込みで状態を確認します。

対象 完了の見え方
記事生成 status が generating → draft(失敗時は failed)
ブログリライト・書き下ろし status が generating → draft(失敗時はレコードごと消える)
サムネイル生成 ブログ本文にサムネイル画像が挿入される

関連情報の生成(related_generate)だけは同期処理で、その場で更新後のブログが返ります。


エラー表示

FastAPI はエラー理由を detail に入れて返します。 src/lib/api.ts の ApiError がこれを message へ引き上げるため、 「このレースは記事を生成できません: …」のようなバックエンドの理由をそのまま画面に表示できます。


デプロイ

bun run build で out/ に静的ファイルを出力し、GitHub Actions(.github/workflows/dev-deploy.yml)が main への push で S3 + CloudFront へ同期します。

SPA のためのフォールバック設定が必要

クライアントルーティングの SPA のため、CloudFront 側で未知パスを index.html(200)に フォールバックさせる必要があります(403/404 のカスタムエラーレスポンスで /index.html を返す)。 設定が無いとディープリンクが 404 になります。