mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
feat: add mise config for web
This commit is contained in:
+4
-1
@@ -1,5 +1,8 @@
|
|||||||
|
[task_config]
|
||||||
|
dir = "{{cwd}}"
|
||||||
|
|
||||||
[tasks.start]
|
[tasks.start]
|
||||||
alias = "dev"
|
alias = "dev"
|
||||||
description = "Start server"
|
description = "Start server"
|
||||||
run = "go run main.go"
|
run = "go run cmd/agent/main.go"
|
||||||
depends = ["//:go-install"]
|
depends = ["//:go-install"]
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ description = "Start development environment"
|
|||||||
depends = [
|
depends = [
|
||||||
"//agent:dev",
|
"//agent:dev",
|
||||||
"//cmd/agent:start",
|
"//cmd/agent:start",
|
||||||
|
"//packages/web:dev",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tasks.setup]
|
[tasks.setup]
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
import { db } from '@memoh/db'
|
|
||||||
import { users, settings } from '@memoh/db/schema'
|
|
||||||
import { eq } from 'drizzle-orm'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证用户凭据
|
|
||||||
* 优先检查是否为 ROOT 用户,否则查询数据库
|
|
||||||
*/
|
|
||||||
export const validateUser = async (username: string, password: string) => {
|
|
||||||
// 检查是否为 ROOT 用户
|
|
||||||
const rootUser = process.env.ROOT_USER
|
|
||||||
const rootPassword = process.env.ROOT_USER_PASSWORD
|
|
||||||
|
|
||||||
let userId: string | null = null
|
|
||||||
|
|
||||||
if (rootUser && rootPassword && username === rootUser) {
|
|
||||||
if (password === rootPassword) {
|
|
||||||
// 检查 root 用户是否存在于数据库中
|
|
||||||
const [existingUser] = await db
|
|
||||||
.select()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.username, rootUser))
|
|
||||||
|
|
||||||
userId = existingUser?.id
|
|
||||||
|
|
||||||
if (!existingUser) {
|
|
||||||
// 为 root 用户创建数据库记录
|
|
||||||
// 使用占位符密码哈希,因为实际密码在环境变量中
|
|
||||||
|
|
||||||
const [newUser] = await db
|
|
||||||
.insert(users)
|
|
||||||
.values({
|
|
||||||
username: rootUser,
|
|
||||||
passwordHash: 'ENV_BASED_AUTH', // 占位符,实际使用环境变量验证
|
|
||||||
role: 'admin',
|
|
||||||
displayName: 'Root User',
|
|
||||||
email: null,
|
|
||||||
avatarUrl: null,
|
|
||||||
isActive: true,
|
|
||||||
})
|
|
||||||
.onConflictDoNothing() // 避免并发创建导致的冲突
|
|
||||||
.returning({
|
|
||||||
id: users.id,
|
|
||||||
})
|
|
||||||
|
|
||||||
userId = newUser.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查 root 用户的 settings 是否存在,不存在则创建
|
|
||||||
const [existingSettings] = await db
|
|
||||||
.select()
|
|
||||||
.from(settings)
|
|
||||||
.where(eq(settings.userId, userId))
|
|
||||||
|
|
||||||
if (!existingSettings) {
|
|
||||||
// 为 root 用户创建默认 settings
|
|
||||||
await db
|
|
||||||
.insert(settings)
|
|
||||||
.values({
|
|
||||||
userId: userId,
|
|
||||||
defaultChatModel: null,
|
|
||||||
defaultEmbeddingModel: null,
|
|
||||||
defaultSummaryModel: null,
|
|
||||||
maxContextLoadTime: 60,
|
|
||||||
language: 'Same as user input',
|
|
||||||
})
|
|
||||||
.onConflictDoNothing() // 避免并发创建导致的冲突
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回 ROOT 用户信息
|
|
||||||
return {
|
|
||||||
id: userId,
|
|
||||||
username: rootUser,
|
|
||||||
role: 'admin' as const,
|
|
||||||
displayName: 'Root User',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询数据库中的用户(使用 username 而不是 id)
|
|
||||||
const [user] = await db
|
|
||||||
.select()
|
|
||||||
.from(users)
|
|
||||||
.where(eq(users.username, username))
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证密码 (这里使用简单的 Bun.password.verify)
|
|
||||||
const isValid = await Bun.password.verify(password, user.passwordHash)
|
|
||||||
|
|
||||||
if (!isValid) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查账户是否激活
|
|
||||||
if (!user.isActive) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新最后登录时间
|
|
||||||
await db
|
|
||||||
.update(users)
|
|
||||||
.set({
|
|
||||||
lastLoginAt: new Date(),
|
|
||||||
})
|
|
||||||
.where(eq(users.id, user.id))
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: user.id,
|
|
||||||
username: user.username,
|
|
||||||
role: user.role,
|
|
||||||
displayName: user.displayName || user.username,
|
|
||||||
email: user.email,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
import Elysia from 'elysia'
|
|
||||||
import { adminMiddleware } from '../../middlewares'
|
|
||||||
import {
|
|
||||||
GetUserByIdModel,
|
|
||||||
CreateUserModel,
|
|
||||||
UpdateUserModel,
|
|
||||||
DeleteUserModel,
|
|
||||||
UpdatePasswordModel,
|
|
||||||
} from './model'
|
|
||||||
import {
|
|
||||||
getUsers,
|
|
||||||
getUserById,
|
|
||||||
createUser,
|
|
||||||
updateUser,
|
|
||||||
deleteUser,
|
|
||||||
updateUserPassword,
|
|
||||||
} from './service'
|
|
||||||
|
|
||||||
export const userModule = new Elysia({
|
|
||||||
prefix: '/user',
|
|
||||||
})
|
|
||||||
// 使用管理员中间件保护所有路由
|
|
||||||
.use(adminMiddleware)
|
|
||||||
// Get all users
|
|
||||||
.get('/', async ({ query }) => {
|
|
||||||
try {
|
|
||||||
const page = parseInt(query.page as string) || 1
|
|
||||||
const limit = parseInt(query.limit as string) || 10
|
|
||||||
const sortBy = query.sortBy as string || 'createdAt'
|
|
||||||
const sortOrder = (query.sortOrder as string) || 'desc'
|
|
||||||
|
|
||||||
const result = await getUsers({
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
sortBy,
|
|
||||||
sortOrder: sortOrder as 'asc' | 'desc',
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
...result,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to fetch users',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// Get user by ID
|
|
||||||
.get('/:id', async ({ params, set }) => {
|
|
||||||
try {
|
|
||||||
const { id } = params
|
|
||||||
const user = await getUserById(id)
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
set.status = 404
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'User not found',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: user,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
set.status = 500
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to fetch user',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, GetUserByIdModel)
|
|
||||||
// Create new user
|
|
||||||
.post('/', async ({ body, set }) => {
|
|
||||||
try {
|
|
||||||
const newUser = await createUser(body)
|
|
||||||
set.status = 201
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: newUser,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error && (
|
|
||||||
error.message.includes('already exists')
|
|
||||||
)) {
|
|
||||||
set.status = 409
|
|
||||||
} else {
|
|
||||||
set.status = 500
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to create user',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, CreateUserModel)
|
|
||||||
// Update user
|
|
||||||
.put('/:id', async ({ params, body, set }) => {
|
|
||||||
try {
|
|
||||||
const { id } = params
|
|
||||||
const updatedUser = await updateUser(id, body)
|
|
||||||
|
|
||||||
if (!updatedUser) {
|
|
||||||
set.status = 404
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'User not found',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: updatedUser,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error && error.message.includes('already exists')) {
|
|
||||||
set.status = 409
|
|
||||||
} else {
|
|
||||||
set.status = 500
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to update user',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, UpdateUserModel)
|
|
||||||
// Delete user
|
|
||||||
.delete('/:id', async ({ params, set }) => {
|
|
||||||
try {
|
|
||||||
const { id } = params
|
|
||||||
const deletedUser = await deleteUser(id)
|
|
||||||
|
|
||||||
if (!deletedUser) {
|
|
||||||
set.status = 404
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'User not found',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: deletedUser,
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
set.status = 500
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to delete user',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, DeleteUserModel)
|
|
||||||
// Update user password
|
|
||||||
.patch('/:id/password', async ({ params, body, set }) => {
|
|
||||||
try {
|
|
||||||
const { id } = params
|
|
||||||
const updatedUser = await updateUserPassword(id, body.password)
|
|
||||||
|
|
||||||
if (!updatedUser) {
|
|
||||||
set.status = 404
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: 'User not found',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: updatedUser,
|
|
||||||
message: 'Password updated successfully',
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
set.status = 500
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : 'Failed to update password',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, UpdatePasswordModel)
|
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[tasks.dev]
|
||||||
|
alias = "dev"
|
||||||
|
description = "Start web development server"
|
||||||
|
run = "pnpm dev"
|
||||||
|
depends = ["//:pnpm-install"]
|
||||||
|
|
||||||
|
[tasks.build]
|
||||||
|
description = "Build web"
|
||||||
|
run = "pnpm build"
|
||||||
|
depends = ["//:pnpm-install"]
|
||||||
|
|
||||||
|
[tasks.start]
|
||||||
|
description = "Start web"
|
||||||
|
run = "pnpm start"
|
||||||
Reference in New Issue
Block a user