Skip to content

Commit 9c273f2

Browse files
committed
feat: add multiple CMS improvements
- Add image compression to migration process - Replace svelte-select with bits-ui (better TypeScript support) - Fix slug input becoming date-only when cleared - Add per-user default author preference - Add slug redirect option when changing article URLs - Add unpublish confirmation warning - Add analytics page and dashboard widgets
1 parent 9a613db commit 9c273f2

File tree

121 files changed

+6972
-1925
lines changed

Some content is hidden

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

121 files changed

+6972
-1925
lines changed

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,5 @@ run `bun tidy` after you finish your work. i.e. before commit
9797

9898
- Never use `as` or `any`. Let TypeScript infer types properly.
9999
- Never just "fire and forget". it crashes the entire server. instead, catch `.catch(console.error)` then forget, if you want to dispatch the job.
100+
101+
For detailed coding standards (import order, async patterns, naming conventions), see `docs/knowledges/coding-standards.md`.

bun.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
# Coding Standards
2+
3+
**Last Updated**: 2025-12-24
4+
5+
## Import Order
6+
7+
インポートは以下の順序で記述する:
8+
9+
1. **外部ライブラリ** - `@sveltejs/kit`, `valibot`, `drizzle-orm`
10+
2. **$app/*** - SvelteKitの内部API (`$app/server`, `$app/environment`等)
11+
3. **$lib/*** - プロジェクトの内部モジュール (階層順)
12+
- `$lib/server/database/*`
13+
- `$lib/server/services/*`
14+
- `$lib/server/drivers/*`
15+
- `$lib/shared/*`
16+
- `$lib/components/*`
17+
4. **型インポート** - `import type { ... }`は最後
18+
19+
###
20+
21+
```ts
22+
// ✅ 正しい順序
23+
import { error } from "@sveltejs/kit";
24+
import * as v from "valibot";
25+
import { command, query } from "$app/server";
26+
import { requireUtCodeMember } from "$lib/server/database/auth.server";
27+
import { createArticle } from "$lib/server/database/articles.server";
28+
import { purgeCache } from "$lib/server/services/cloudflare/cache.server";
29+
import type { Article } from "$lib/shared/models/types";
30+
31+
// ❌ 誤った順序
32+
import { createArticle } from "$lib/server/database/articles.server";
33+
import type { Article } from "$lib/shared/models/types";
34+
import { error } from "@sveltejs/kit";
35+
import { command, query } from "$app/server";
36+
```
37+
38+
## Async/Await パターン
39+
40+
### Fire-and-Forget パターン
41+
42+
非同期処理を「発射して忘れる」場合、必ず`.catch(console.error)`を使用する。
43+
44+
```ts
45+
// ✅ 正しいパターン
46+
purgeCache().catch(console.error);
47+
48+
db.update(article)
49+
.set({ viewCount: sql`${article.viewCount} + 1` })
50+
.where(eq(article.slug, slug))
51+
.catch(console.error);
52+
53+
// ❌ 誤ったパターン (サーバークラッシュの原因)
54+
purgeCache(); // エラーがキャッチされない
55+
56+
// ❌ 冗長なパターン
57+
purgeCache()
58+
.then(() => {})
59+
.catch(console.error);
60+
61+
// ❌ 古いパターン (Prettierが自動修正)
62+
purgeCache().then(() => {}, console.error);
63+
```
64+
65+
**理由**: サーバー環境では、catchされないPromiseリジェクションはサーバー全体をクラッシュさせる可能性がある。
66+
67+
### Async/Await の統一
68+
69+
非同期関数は一貫して`async/await`を使用する。`.then()`チェーンは避ける。
70+
71+
```ts
72+
// ✅ 正しいパターン
73+
export const getArticles = query(async () => {
74+
await requireUtCodeMember();
75+
return await listAllArticles();
76+
});
77+
78+
// ❌ 混在パターン (避ける)
79+
export const getArticles = query(() => {
80+
return requireUtCodeMember().then(() => listAllArticles());
81+
});
82+
```
83+
84+
## エラーハンドリング
85+
86+
### Remote Functions
87+
88+
Remote functionsでは、エラーは自動的にクライアントに伝播されるため、try-catchは不要。
89+
90+
```ts
91+
// ✅ シンプルなパターン
92+
export const deleteArticle = command(v.string(), async (id) => {
93+
const session = await requireUtCodeMember();
94+
await requireArticleOwnership(session, id);
95+
await deleteArticle(id);
96+
purgeCache().catch(console.error);
97+
});
98+
99+
// ❌ 不要なtry-catch
100+
export const deleteArticle = command(v.string(), async (id) => {
101+
try {
102+
const session = await requireUtCodeMember();
103+
await requireArticleOwnership(session, id);
104+
await deleteArticle(id);
105+
} catch (error) {
106+
throw error; // 不要
107+
}
108+
});
109+
```
110+
111+
例外: ビジネスロジックで特定のエラーを変換する場合のみtry-catchを使用。
112+
113+
### Database層
114+
115+
Database層では、エラーメッセージを明確にする。
116+
117+
```ts
118+
// ✅ 明確なエラーメッセージ
119+
export async function createArticle(data: NewArticle) {
120+
const [created] = await db.insert(article).values(data).returning();
121+
if (!created) throw new Error("Failed to create article");
122+
return created;
123+
}
124+
125+
// ❌ 曖昧なエラー
126+
export async function createArticle(data: NewArticle) {
127+
const [created] = await db.insert(article).values(data).returning();
128+
return created; // undefinedの可能性
129+
}
130+
```
131+
132+
## 命名規則
133+
134+
### ファイル命名
135+
136+
- **Remote Functions**: `*.remote.ts`
137+
- **Server Functions**: `*.server.ts`
138+
- **Shared Logic**: `*.ts` (サフィックスなし)
139+
- **Tests**: `*.test.ts`
140+
- **Types**: `types.ts` または `schema.ts`
141+
142+
### 関数命名
143+
144+
- **Remote Query**: 名詞形 (`getArticles`, `getMember`)
145+
- **Remote Command**: 動詞形 (`saveArticle`, `editMember`, `removeProject`)
146+
- **Server Functions**: 動詞形 (`listArticles`, `createMember`, `updateProject`)
147+
148+
```ts
149+
// Remote Functions (DAL)
150+
export const getArticles = query(async () => ...);
151+
export const saveArticle = command(..., async (data) => ...);
152+
153+
// Server Functions (DB)
154+
export async function listArticles() { ... }
155+
export async function createArticle(data: NewArticle) { ... }
156+
```
157+
158+
## Valibot バリデーション
159+
160+
### Picklist の使用
161+
162+
列挙型の値は`v.picklist()`を使用する。
163+
164+
```ts
165+
// ✅ 型安全なpicklist
166+
const PROJECT_CATEGORY_VALUES = [
167+
"active",
168+
"ended",
169+
"hackathon",
170+
"festival",
171+
"personal",
172+
] as const satisfies readonly ProjectCategory[];
173+
174+
const categorySchema = v.picklist(PROJECT_CATEGORY_VALUES);
175+
176+
// ❌ 型安全でないパターン
177+
const categorySchema = v.picklist(["active", "ended", "hackathon"]);
178+
```
179+
180+
## フォーマット
181+
182+
### インデント
183+
184+
- **タブ文字**を使用 (Prettier設定: `useTabs: true`)
185+
- スペースは使用しない
186+
187+
### 行の長さ
188+
189+
- 最大120文字 (Prettier設定: `printWidth: 120`)
190+
- 長いインポートや関数呼び出しは自動で折り返される
191+
192+
### セミコロン
193+
194+
- 常にセミコロンを使用 (Prettier設定: `semi: true`)
195+
196+
### クォート
197+
198+
- ダブルクォートを使用 (Prettier設定: `useTabs: true`)
199+
200+
## ツール
201+
202+
### 自動フォーマット
203+
204+
```sh
205+
bun tidy # type-check + test-check + biome + prettier
206+
bun fix # biome + prettier のみ
207+
```
208+
209+
### 個別チェック
210+
211+
```sh
212+
bun type-check # TypeScript型チェック
213+
bun test-check # 単体テスト
214+
bun lint-check # Biomeリント
215+
bun format-check # Prettierフォーマット
216+
```
217+
218+
## 検証ルール
219+
220+
コミット前に必ず以下を実行:
221+
222+
1. `bun type-check` - エラー0であること
223+
2. `bun tidy` - エラー・警告0であること
224+
225+
## 参考
226+
227+
- CLAUDE.md - プロジェクト固有のワークフロー
228+
- security.md - セキュリティ設計
229+
- project-context.md - プロジェクト概要

0 commit comments

Comments
 (0)