UI for Admin — セットアップ
管理者 UI は Orval を用いて管理者 API の OpenAPI 3.1 スペック(/api/v1/doc)から以下を自動生成する:
- TypeScript 型(リクエスト / レスポンス / コンポーネント)
- TanStack Query フック(
useGetAdmins、useCreateAdminなど) - Zod スキーマ(フォームバリデーションに利用)
Orval 設定
// orval.config.ts
export default {
adminApi: {
input: 'http://localhost:8080/api/v1/doc',
output: {
target: 'src/api/generated/admin.ts',
client: 'react-query',
schemas: 'src/api/generated/model',
httpClient: 'fetch',
override: {
mutator: { path: 'src/api/client.ts', name: 'adminFetcher' },
},
},
},
adminApiZod: {
input: 'http://localhost:8080/api/v1/doc',
output: {
target: 'src/api/generated/admin.zod.ts',
client: 'zod',
},
},
}
フェッチャー(src/api/client.ts)
- 同一オリジン の
/api/proxy/<path>に向ける(管理者 API に直接アクセスしない) credentials: 'same-origin'で httpOnly Cookie を自動送信- レスポンスエンベロープ正規化: 管理者 API は
{ success, data }と旧トップレベル形式が混在中(API for Admin 参照)。フェッチャーでラップを吸収しフックには常に unwrap 済みペイロードを渡す - 401 リトライ:
/api/proxy/<path>が 401 を返したとき、単一の in-flight refresh Promise を作成し並列リクエストをコアレッシング。成功したらオリジナルリクエストをリプレイ
// src/api/client.ts
let refreshPromise: Promise<void> | null = null
export async function adminFetcher<T>(config: AdminFetchConfig): Promise<T> {
const res = await fetchWithCookies(config)
if (res.status !== 401) return normalizeEnvelope(res)
refreshPromise ??= refreshSession().finally(() => { refreshPromise = null })
await refreshPromise
const retried = await fetchWithCookies(config)
return normalizeEnvelope(retried)
}
API クライアント再生成
| コマンド | 用途 |
|---|---|
bun run generate:api |
ローカル開発中。管理者 API を bun run dev-admin で立ち上げてから実行 |
| CI チェック | 生成物が OpenAPI と一致するかを比較。不一致ならビルド失敗 |
OpenAPI スペック入手方法(リポジトリ間連携)
管理者 API は別リポジトリ(GenAI-Guinness-backend)にあるため、OpenAPI スペックの取得方法を決めておく必要がある。
| 方法 | タイミング | メリット / デメリット |
|---|---|---|
| ローカルバックエンドを起動して取得 | 開発中 | 最新仕様を即反映。開発者にバックエンドのセットアップが必要 |
| バックエンド CI が OpenAPI JSON をアーティファクトとして公開し、UI 側でバージョンをピン留め | ステージング以降 | 再現性。バージョンずれのリスク |
デフォルト方針: 開発中は方法 1。バックエンドが v1 で安定したら方法 2 に移行。