Skip to content

Commit 94c061c

Browse files
committed
vite proxy configuration, onAuthChange store, auto login and some more
1 parent e5d47ae commit 94c061c

File tree

25 files changed

+249
-132
lines changed

25 files changed

+249
-132
lines changed

.env.sample

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
# Config
2-
PUBLIC_SERVER_URL=http://localhost:4000
32
PUBLIC_MOCK_DATA=true
4-
SERVER_PORT=4000 # optional, server port (alias: PORT, PUBLIC_SERVER_URL.port) (defaults to 4000 if none are specified)
53

6-
# Secrets
7-
PUBLIC_X_ANON_KEY=...
8-
X_API_KEY=...
4+
# 開発中 -> Vite: BASE_URL.port, Elysia: EXTERNAL_SERVER_PORT
5+
# プロダクション: Vite: - , Elysia: PORT ?? BASE_URL.port
6+
BASE_URL=http://localhost:3000
7+
EXTERNAL_SERVER_PORT=4000
98

9+
NODE_ENV=development
10+
11+
# Database
1012
DATABASE_URL=file:../../local.db
1113

14+
# Better Auth
1215
BETTER_AUTH_SECRET=...
13-
BETTER_AUTH_URL=http://localhost:4000
1416

1517
GOOGLE_CLIENT_ID=...
1618
GOOGLE_CLIENT_SECRET=
17-
18-
PUBLIC_WEB_URL=http://localhost:3000

docs/developer_readme.md

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ bun db push
1616

1717
```bash
1818
# 開発モードを実行するには、以下のコマンドを実行してください。
19-
# localhost:5173 に Vite サーバーが立ち上がるので、そこで確認してください。
19+
# $BASE_URL に Vite サーバーが立ち上がるので、そこで確認してください。
2020
bun dev
2121

2222
# Storybookの使用
23-
# localhost:6006にStorybookが立ち上がるので、そこでUIを確認してください。
23+
# localhost:6006 にStorybookが立ち上がるので、そこでUIを確認してください。
2424
bun run storybook
2525
```
2626

@@ -36,28 +36,26 @@ bun check
3636
bun fix
3737
```
3838

39-
## モックモード
39+
## サーバー構成
4040

41-
モックモードを実行するには、以下のコマンドを実行してください。
41+
### 1. 開発中
4242

43-
```bash
44-
bun dev:mock
45-
```
43+
- Vite 開発サーバー (localhost:3000) -> すべてのリクエストはこのサーバーがプロキシ (ログインのリダイレクトを除く)
44+
- Elysia サーバー (localhost:4000) -> /api 以下のリクエストをプロキシされて受け取る
4645

47-
このコマンドを実行すると、モックデータを使用してアプリケーションが実行されます。
46+
### 2. プロダクション
4847

49-
## 関数やクラスの説明
48+
- Elysia サーバー (localhost:${PORT}) -> /api 以外は public/index.html を返す
5049

51-
### 1. Userのデータを扱う場合 (src/app/utils/user.ts)
50+
## モックモード
5251

53-
Userのデータは`User`クラスを使用して扱います。Userのデータは以下の場合があります
52+
モックモードを実行するには、以下のコマンドを実行してください
5453

55-
- `bun dev:mock`を実行した場合
56-
- Userはモックのデータが使用されます。
57-
- `bun dev`を実行した場合
58-
- UserはlocalStorageに保存されたデータが使用されます。
54+
```bash
55+
PUBLIC_MOCK_DATA=true bun dev
56+
```
5957

60-
ただ、まだユーザを登録する機能がないので、mockでユーザを作成する必要があります
58+
このコマンドを実行すると、モックデータを使用してアプリケーションが実行されます
6159

6260
## 推奨 VS Code 設定
6361

packages/models/models.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import {
1313
Stream,
1414
} from "./atoms.ts";
1515

16-
export type User = typeof User.static;
17-
export const User = t.Object({
16+
export type UserData = typeof UserData.static;
17+
export const UserData = t.Object({
1818
stream: Stream,
1919
grade: t.Number(),
2020
classNumber: t.Number(),

packages/server/app.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { cors } from "@elysiajs/cors";
22
import { Elysia } from "elysia";
3-
import { betterAuth } from "@/lib/auth.ts";
3+
import { auth, betterAuth } from "./lib/auth.ts";
44
import coursesRouter from "./router/courses.ts";
55
import usersRouter from "./router/users.ts";
66

77
export const app = new Elysia({
88
prefix: "/api",
99
})
10+
.mount(auth.handler)
1011
.use(cors())
1112
.use(betterAuth)
1213
.use(coursesRouter)

packages/server/db/schema.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,9 @@ export const userData = sqliteTable("user_data", {
2424
stream: text("stream"),
2525
grade: integer("grade"),
2626
classNumber: integer("class_number"),
27-
});
28-
export const userCourse = sqliteTable("user_courses", {
29-
id: text("id").primaryKey(),
30-
userId: text("user_id")
31-
.notNull()
32-
.references(() => user.id, { onDelete: "cascade" }),
33-
code: text("code").notNull(),
27+
courses: text("courses", { mode: "json" }).notNull(),
3428
});
3529
export const userRelations = relations(user, ({ one }) => ({
36-
courses: one(userCourse, {
37-
fields: [user.id],
38-
references: [userCourse.userId],
39-
}),
4030
data: one(userData, {
4131
fields: [user.id],
4232
references: [userData.userId],

packages/server/lib/auth.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as schema from "../db/schema.ts";
66
import { env } from "../lib/env.ts";
77

88
export const auth = betterAuth({
9+
baseURL: env.BASE_URL,
910
database: drizzleAdapter(db, {
1011
provider: "sqlite",
1112
schema: schema,
@@ -16,27 +17,25 @@ export const auth = betterAuth({
1617
clientSecret: env.GOOGLE_CLIENT_SECRET,
1718
},
1819
},
19-
trustedOrigins: [env.PUBLIC_WEB_URL],
20+
trustedOrigins: [env.BASE_URL],
2021
advanced: {
2122
cookiePrefix: "x_utcode_syllabus_",
2223
},
2324
});
2425

25-
const betterAuthMacro = new Elysia({ name: "better-auth" })
26-
.mount(auth.handler)
27-
.macro({
28-
auth: {
29-
async resolve({ status, request: { headers } }) {
30-
const session = await auth.api.getSession({
31-
headers,
32-
});
33-
if (!session) return status(401);
26+
const betterAuthMacro = new Elysia({ name: "better-auth" }).macro({
27+
auth: {
28+
async resolve({ status, request: { headers } }) {
29+
const session = await auth.api.getSession({
30+
headers,
31+
});
32+
if (!session) return status(401);
3433

35-
return {
36-
user: session.user,
37-
session: session.session,
38-
};
39-
},
34+
return {
35+
user: session.user,
36+
session: session.session,
37+
};
4038
},
41-
});
39+
},
40+
});
4241
export { betterAuthMacro as betterAuth };

packages/server/lib/env.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import * as e from "./utils/environment.ts";
2+
import { panic } from "./utils/panic.ts";
23

34
export const env = {
45
DATABASE_URL: e.string("DATABASE_URL"),
5-
PUBLIC_WEB_URL: e.string("PUBLIC_WEB_URL"),
66
GOOGLE_CLIENT_ID: e.string("GOOGLE_CLIENT_ID"),
77
GOOGLE_CLIENT_SECRET: e.string("GOOGLE_CLIENT_SECRET"),
88

9-
SERVER_PORT: e.string_optional("SERVER_PORT"),
10-
PORT: e.string_optional("PORT"),
11-
PUBLIC_SERVER_URL: e.string_optional("PUBLIC_SERVER_URL"),
9+
LISTEN_PORT: getListenPort() ?? panic("[env] Port to listen not found"),
10+
BASE_URL: e.string("BASE_URL"),
1211

1312
PUBLIC_MOCK_DATA: e.boolean("PUBLIC_MOCK_DATA"),
1413
};
14+
15+
function getListenPort() {
16+
if (e.string("NODE_ENV") === "development") {
17+
return e.string_optional("EXTERNAL_SERVER_PORT");
18+
}
19+
return e.string_optional("PORT") ?? new URL(e.string("BASE_URL")).port;
20+
}

packages/server/lib/utils/environment.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,13 @@ export function boolean(name: string, fallback?: boolean): boolean {
1919
if (fallback !== undefined) return fallback;
2020
panic(`Environment variable ${name} not found`);
2121
}
22+
23+
export function array(name: string, splitter: string): string[] {
24+
return string(name).split(splitter);
25+
}
26+
export function array_optional(
27+
name: string,
28+
splitter: string,
29+
): string[] | undefined {
30+
return string_optional(name)?.split(splitter);
31+
}

packages/server/router/users.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import { CourseCode, UserData } from "@packages/models";
2+
import { Value } from "@sinclair/typebox/value";
13
import { eq } from "drizzle-orm";
2-
import Elysia from "elysia";
3-
import { db } from "@/db/client";
4-
import { betterAuth } from "@/lib/auth.ts";
4+
import Elysia, { status, t } from "elysia";
5+
import { db } from "../db/client.ts";
6+
import { userData } from "../db/schema.ts";
7+
import { betterAuth } from "../lib/auth.ts";
8+
import type { SelectUser } from "../types.ts";
59

610
const router = new Elysia({
711
prefix: "/users",
@@ -14,14 +18,49 @@ const router = new Elysia({
1418
where: (user) => eq(user.id, currentUserId),
1519
with: {
1620
data: true,
17-
courses: true,
1821
},
1922
});
20-
return fullUser;
23+
if (!fullUser) return status(404, "User not found");
24+
25+
try {
26+
if (fullUser.data) {
27+
Value.Assert(t.Array(CourseCode), fullUser.data.courses);
28+
}
29+
return <SelectUser>fullUser;
30+
} catch {
31+
// it's not recoverable - reset courses
32+
console.error("Failed to parse courses:", fullUser.data?.courses);
33+
await db
34+
.update(userData)
35+
.set({
36+
courses: [],
37+
})
38+
.where(eq(userData.userId, currentUserId));
39+
return status(500, "Internal server error: Failed to parse courses");
40+
}
41+
},
42+
{
43+
auth: true,
44+
},
45+
)
46+
.put(
47+
"/me/data",
48+
async ({ user: { id: currentUserId }, body }) => {
49+
await db
50+
.update(userData)
51+
.set({
52+
stream: body.stream,
53+
grade: body.grade,
54+
classNumber: body.classNumber,
55+
courses: body.courses,
56+
})
57+
.where(eq(userData.userId, currentUserId));
2158
},
2259
{
2360
auth: true,
61+
body: UserData,
2462
},
2563
);
64+
// TODO: 画像を更新する
2665

2766
export default router;

packages/server/serve.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
11
import { app } from "./app.ts";
22
import { env } from "./lib/env.ts";
33

4-
const port =
5-
env.SERVER_PORT ?? env.PORT ?? parsePort(env.PUBLIC_SERVER_URL) ?? "4000";
6-
7-
app.listen(port, () =>
8-
console.log(`Server started at http://localhost:${port}`),
4+
app.listen(env.LISTEN_PORT, () =>
5+
console.log(`Server started at http://localhost:${env.LISTEN_PORT}`),
96
);
10-
11-
function parsePort(urlstr: string | undefined) {
12-
if (!urlstr) return undefined;
13-
const url = new URL(urlstr);
14-
return url.port;
15-
}

0 commit comments

Comments
 (0)