ディレクトリ構成
リポジトリ全体
arrange/
├── heineken-interview-arrange-backend/ # Bun + Hono の REST API
├── heineken-interview-arrange-frontend/ # Next.js App Router の管理画面
└── heineken-interview-arrange-docs/ # 本ドキュメント(MkDocs)
両者は OpenAPI 経由でのみ結合している。バックエンドがルート定義から /openapi.json を自動生成し、フロントは Orval でそこから React Query フックと型を生成する。
backend
heineken-interview-arrange-backend/
├── src/
│ ├── app.ts # Hono アプリの組み立て(CORS・OpenAPI・Swagger UI・404/エラー)
│ ├── server.ts # エントリポイント
│ ├── error.ts # 業務固有の AppError サブクラス
│ ├── routes/
│ │ ├── index.ts
│ │ └── v1/ # HTTP 層。createRoute() で OpenAPI 定義とハンドラを対で書く
│ ├── schemas/ # リクエスト/レスポンスの Zod スキーマ(公開フィールドは snake_case)
│ ├── services/ # ビジネスロジック
│ ├── repositories/ # Drizzle クエリのみ
│ ├── db/
│ │ └── schema.ts # 全テーブル・全 enum・ドメイン型の単一定義元
│ ├── lib/ # response / errors / logger / pagination / date / databricks-client など
│ ├── middlewares/ # protected-route / error-handler / request-id / winston-logger
│ ├── config/ # env / database / google / business-hours
│ ├── types/ # インポート関連の型
│ └── batch/ # API とは独立して動くバッチ(import / databricks-meta-sync / pii-cleanup)
├── __tests__/
│ └── unit/services/ # Vitest の単体テスト
├── scripts/ # setup.sh / migrate.ts / seed.ts
├── drizzle.config.ts
├── docker-compose.yml # PostgreSQL 17
├── Dockerfile # アプリ(Lambda + aws-lambda-adapter)
├── Dockerfile.migration # マイグレーション Lambda
└── Dockerfile.sync # 同期バッチ(ECS Fargate)
レイヤ構成
routes → services → repositories → Drizzle の一方向依存。
| ディレクトリ | 責務 | してはいけないこと |
|---|---|---|
src/routes/v1/ |
HTTP 層。OpenAPI 定義、認証・ロール制御、snake_case ↔ camelCase の変換 | ビジネスロジックを書く、Drizzle を直接呼ぶ |
src/schemas/ |
Zod スキーマ。API の公開フィールドは snake_case | DB のカラム名をそのまま公開する |
src/services/ |
ビジネスロジック。export * as xxxService で services/index.ts から名前空間として再エクスポート |
Hono の Context に依存する |
src/repositories/ |
Drizzle クエリのみ。失敗は DatabaseQueryError などに包んで投げる |
業務判断を行う |
src/db/schema.ts |
全テーブル・enum・$inferSelect / $inferInsert 由来の型の単一定義元 |
複数ファイルに分割する |
ESM の拡張子
import は .js 拡張子付きが必須(from '../lib/index.js')。TypeScript のソースを指す場合も .js と書く。
認証・ロール制御の適用箇所
ルータ単位で適用する。個別ハンドラでは書かない。
candidatesRoutes.use('*', protectedRoute());
candidatesRoutes.on(['POST', 'PATCH', 'PUT', 'DELETE'], ['/*'], requireRole('member'));
frontend
heineken-interview-arrange-frontend/
├── src/
│ ├── app/
│ │ ├── (dashboard)/ # 認証が要る管理画面
│ │ │ ├── page.tsx # ダッシュボード
│ │ │ ├── projects/ # プロジェクト一覧・作成・詳細(アレンジフロー)
│ │ │ └── surveys/ # アンケート閲覧
│ │ ├── login/ # ログイン
│ │ └── scheduling/[token]/ # 候補者向け公開ページ(認証なし)
│ ├── api/ # Orval 生成物。手で編集しない
│ │ ├── custom-fetch.ts # Bearer 付与・{ data, status, headers } 形で返す
│ │ ├── endpoints/ # タグ別のフック
│ │ └── models/ # 型定義
│ ├── components/
│ │ ├── ui/ # 汎用 UI(button / table / modal / slot-calendar など)
│ │ ├── layout/ # app-layout / header / sidebar
│ │ ├── projects/ # arrange-steps / step1-form / dashboard-stats
│ │ ├── step2/ # segment-sidebar / candidate-table / question-filter
│ │ ├── candidates/ # candidate-row / summary-bar / status-helpers
│ │ └── providers.tsx # React Query などの共有プロバイダ
│ ├── lib/ # auth / utils / candidate-status / response-filter
│ └── types/
│ └── arrange-settings.ts # backend と二重定義されるアレンジ設定の型
├── orval.config.ts
├── next.config.ts
└── Dockerfile
各ディレクトリの説明
| ディレクトリ | 説明 |
|---|---|
src/app/(dashboard)/ |
認証が必要な管理画面。ルートグループなので URL には現れない |
src/app/scheduling/[token]/ |
トークンのみでアクセスする公開ページ。認証しない |
src/api/ |
Orval 生成物。useGetApiV1ProjectsProjectId のようにパスから命名される |
src/components/ui/ |
再利用可能な UI コンポーネント |
src/lib/ |
認証トークンの出入口(auth.ts)とユーティリティ |
src/types/ |
OpenAPI で表現されない型(JSON カラムの中身など) |
API レスポンスの取り出し
custom-fetch.ts が { data, status, headers } 形で返し、その data が API のエンベロープ({ success, data, ... })なので、画面側は response.data.data を辿ることになる。lib/utils.ts の apiData() ヘルパーを使う。
const { data } = useGetApiV1Projects();
const projects = apiData<GetApiV1Projects200>(data);
// projects?.data → Project[]
新ファイルを追加するとき
backend
| 追加するもの | 置き場所 |
|---|---|
| 新しいエンドポイント | src/routes/v1/<resource>.ts(既存ルータに追記、または新規作成して routes/v1/index.ts に登録) |
| リクエスト/レスポンス定義 | src/schemas/<resource>.ts |
| ビジネスロジック | src/services/<resource>.ts → services/index.ts に再エクスポート |
| DB クエリ | src/repositories/<resource>.ts |
| テーブル・enum・ドメイン型 | src/db/schema.ts(単一ファイル) |
| 環境変数 | src/config/env.ts と .env.example の両方 |
| 汎用エラー | src/lib/errors.ts |
| 業務固有エラー | src/error.ts |
| バッチ | src/batch/<name>.ts |
frontend
| 追加するもの | 置き場所 |
|---|---|
| 画面 | src/app/(dashboard)/**/page.tsx |
| 汎用 UI コンポーネント | src/components/ui/ |
| 画面固有コンポーネント | src/components/<画面名>/ |
| API 呼び出し | 追加しない(bun run generate-api で src/api/** を再生成する) |
| 型定義 | src/types/ |