Skip to content

Commit 4fa9d2a

Browse files
authored
Merge pull request #3170 from AtCoder-NoviSteps/#3169
refactor: Move and reconfigure contest table to features (#3169)
2 parents 9c718c6 + bde5d32 commit 4fa9d2a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+5043
-4592
lines changed

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,4 @@ prisma/schema.prisma # Database schema
5757
- See `package.json` for versions and scripts
5858
- See `prisma/schema.prisma` for database models
5959
- See `docs/guides/` for detailed documentation
60+
- See `docs/guides/architecture.md` for directory structure and colocation guide

docs/guides/architecture.md

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# アーキテクチャガイド
2+
3+
## 1. 3層ディレクトリモデル
4+
5+
コードの配置先を **routes / features / lib** の3層で判断する。
6+
7+
```text
8+
src/
9+
├── routes/ # ルート定義。薄く保つ
10+
├── features/ # 機能スコープのコード(1つの機能ドメインに閉じるもの)
11+
└── lib/ # 真に共通のコード(2つ以上の機能から使われるもの)
12+
```
13+
14+
### 判定基準
15+
16+
```text
17+
このコードはどの機能ドメインで使われるか?
18+
19+
├── 1つの機能ドメインに属する
20+
│ → features/{feature}/ に配置
21+
22+
└── 複数の機能ドメインで使う
23+
→ lib/ に配置
24+
```
25+
26+
`routes/` にはルーティングファイル(`+page.svelte`, `+page.server.ts`, `+layout.svelte` 等)のみ置く。
27+
28+
**例:**
29+
30+
- `WorkBookForm.svelte` → workbooks ドメイン → `features/workbooks/`
31+
- `TaskTable.svelte` → tasks ドメイン(problems ページで表示) → `features/tasks/`
32+
- `GradeLabel.svelte` → problems・workbooks・admin で使う → `lib/components/`
33+
- `UserService` → auth・admin・users で使う → `lib/server/services/`
34+
35+
---
36+
37+
## 2. src/features/ の構造
38+
39+
feature ディレクトリの内部は技術スタックベースで整理する。`components/` はページ単位でサブディレクトリに分割し、テストはファイル隣接で配置する。
40+
41+
```text
42+
src/features/
43+
├── workbooks/
44+
│ ├── components/
45+
│ │ ├── list/ # 一覧ページ
46+
│ │ │ ├── WorkBookList.svelte
47+
│ │ │ ├── WorkBookList.test.ts
48+
│ │ │ └── WorkBookBaseTable.svelte
49+
│ │ ├── detail/ # 詳細ページ
50+
│ │ │ ├── CommentAndHint.svelte
51+
│ │ │ └── PublicationStatusLabel.svelte
52+
│ │ ├── form/ # 作成・編集ページ
53+
│ │ │ ├── WorkBookForm.svelte
54+
│ │ │ ├── WorkBookForm.test.ts
55+
│ │ │ └── WorkBookInputFields.svelte
56+
│ │ └── shared/ # feature 内で複数ページから使うもの
57+
│ ├── fixtures/ # テスト用データ
58+
│ ├── services/
59+
│ │ ├── workbooks.ts
60+
│ │ └── workbooks.test.ts
61+
│ ├── stores/
62+
│ │ └── active_workbook_tab.ts
63+
│ ├── types/
64+
│ │ └── workbook_form.ts
65+
│ └── utils/
66+
│ ├── workbook.ts
67+
│ └── workbook.test.ts
68+
├── tasks/
69+
│ ├── components/
70+
│ │ ├── contest-table/ # コンテスト別
71+
│ │ ├── grade-list/ # グレード別
72+
│ │ ├── detail/
73+
│ │ │ ├── statistics/ # AtCoder Problems の difficulty、JOI 難易度、AC人数 など
74+
│ │ │ ├── votes/ # AtCoder NoviSteps の難易度、タグを投票
75+
│ │ │ └── comments/ # (要確認) 管理者: 解説、一般ユーザ: コメント
76+
│ │ └── shared/
77+
├── admin/
78+
│ ├── components/
79+
│ │ ├── account_transfer/
80+
│ │ ├── tasks-for-import/
81+
│ │ └── shared/
82+
│ ...
83+
├── auth/
84+
└── user-profile/
85+
```
86+
87+
**components/ のサブディレクトリ規約:**
88+
89+
- `list/` — 一覧ページ用コンポーネント
90+
- `detail/` — 詳細ページ用コンポーネント
91+
- `form/` — 作成・編集フォーム用コンポーネント
92+
- `shared/` — feature 内で複数ページから使うコンポーネント
93+
94+
### Feature 分割案
95+
96+
現在の `src/lib/` からの抽出候補:
97+
98+
| Feature | 抽出対象 |
99+
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
100+
| **workbooks** | `WorkBook/*`, `WorkBooks/*`, `services/workbooks.ts`, `utils/workbook*.ts`, `stores/active_workbook_tab.ts` |
101+
| **tasks** | `TaskTables/*`, `TaskGrades/*`, `stores/active_problem_list_tab.svelte.ts`(※ `TaskGradeList`, `TaskList` 等は複数ドメインで使うため `lib/` に残す) |
102+
| **admin** | `TagForm`, `TagListForEdit`, `TaskForm`, `TaskListForEdit`, `services/tags.ts`, `services/task_tags.ts` |
103+
| **auth** | `AuthForm`, `utils/auth_forms.ts`, `types/auth_forms.ts` |
104+
| **user-profile** | `UserProfile`, `UserAccountDeletionForm` |
105+
106+
### Feature 間の依存ルール
107+
108+
- `features/``lib/` の参照: OK
109+
- `features/A/``features/B/` の参照: NG(共通化が必要なら `lib/` に移す)
110+
- `routes/``features/` の参照: OK
111+
- `routes/``lib/` の参照: OK
112+
113+
---
114+
115+
## 3. src/lib/ に残すもの
116+
117+
2つ以上の feature から使われるコードのみ配置する。
118+
119+
```text
120+
src/lib/
121+
├── actions/ # SvelteKit アクション
122+
├── clients/ # 外部 API クライアント(AtCoder Problems, AOJ)
123+
├── components/ # 共通 UI コンポーネント(GradeLabel, TaskGradeList, TaskList, FormWrapper 等)
124+
├── constants/ # アプリ定数
125+
├── server/ # サーバー専用コード
126+
│ ├── auth.ts
127+
│ ├── database.ts
128+
│ └── services/ # 複数 feature で使うビジネスロジック
129+
├── stores/ # 共通ストア(error_message 等)
130+
├── types/ # 共通型定義
131+
├── utils/ # 共通ユーティリティ
132+
└── zod/ # バリデーションスキーマ
133+
```
134+
135+
`lib/server/` 内のコードはクライアントからインポートできない(SvelteKit が自動でビルド時エラーにする)。
136+
137+
---
138+
139+
## 4. テストコロケーション
140+
141+
### 方針: ファイル隣接配置
142+
143+
テストは対象ファイルの隣に置く。変更時にテストが目に入り、更新忘れを防ぐ。
144+
145+
```text
146+
features/workbooks/services/
147+
├── workbooks.ts
148+
└── workbooks.test.ts ← 隣接
149+
```
150+
151+
### 共通テスト資産は src/test/ に残す
152+
153+
- テストファクトリ(`@quramy/prisma-fabbrica`
154+
- テストヘルパー・フィクスチャ
155+
- E2E テスト(`tests/`
156+
157+
### Vitest 設定
158+
159+
`vite.config.ts``include` パターンを更新する:
160+
161+
```ts
162+
include: [
163+
'src/test/**/*.test.ts', // 既存テスト(段階移行)
164+
'src/features/**/*.test.ts', // feature 内テスト
165+
];
166+
```
167+
168+
---
169+
170+
## 5. パスエイリアス
171+
172+
`svelte.config.js` でカスタムエイリアスを設定する:
173+
174+
```js
175+
kit: {
176+
alias: {
177+
$lib: './src/lib',
178+
$features: './src/features',
179+
},
180+
}
181+
```
182+
183+
```ts
184+
// インポート例
185+
import WorkBookForm from '$features/workbooks/components/WorkBookForm.svelte';
186+
import { formatDate } from '$lib/utils/format';
187+
import type { PageServerLoad } from './$types'; // フレームワーク型は ./$types から
188+
```
189+
190+
`$types` は SvelteKit が `.svelte-kit/types/` に自動生成するフレームワーク型(`PageServerLoad` 等)。ビジネスドメイン型は `$lib/types/` に置く。
191+
192+
---
193+
194+
## 6. 参考資料
195+
196+
### SvelteKit 公式
197+
198+
- [プロジェクト構造](https://svelte.dev/docs/kit/project-structure)
199+
- [ルーティング](https://svelte.dev/docs/kit/routing)
200+
- [高度なルーティング](https://svelte.dev/docs/kit/advanced-routing)
201+
- [サーバー専用モジュール](https://svelte.dev/docs/kit/server-only-modules)
202+
203+
### 設計の参考
204+
205+
- [Next.js App Router アーキテクチャ設計ガイド](https://zenn.dev/yukionishi/articles/cd79e39ea6c172)`src/features/` パターン、ESLint によるアーキテクチャ境界の自動強制
206+
- [サンプルリポジトリ](https://github.com/YukiOnishi1129/next-app-router-architecture) — 上記記事の実装例
207+
- [Immich](https://github.com/immich-app/immich/tree/main/web/src) — 大規模 SvelteKit アプリの実例(`lib/components/{domain}/` パターン)
208+
209+
### 関連 Issue
210+
211+
- [#601: ディレクトリ構造&コロケーション](https://github.com/AtCoder-NoviSteps/AtCoderNoviSteps/issues/601)

0 commit comments

Comments
 (0)