實作管理者用戶管理、邀請註冊功能
This commit is contained in:
33
app/api/admin/dashboard/route.ts
Normal file
33
app/api/admin/dashboard/route.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { UserService } from '@/lib/services/database-service'
|
||||
|
||||
const userService = new UserService()
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// 獲取儀表板統計數據
|
||||
const stats = await userService.getDashboardStats()
|
||||
|
||||
// 獲取最新活動
|
||||
const recentActivities = await userService.getRecentActivities(10)
|
||||
|
||||
// 獲取熱門應用
|
||||
const topApps = await userService.getTopApps(5)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
stats,
|
||||
recentActivities,
|
||||
topApps
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('獲取儀表板數據錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '獲取儀表板數據時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
46
app/api/admin/users/[id]/activities/route.ts
Normal file
46
app/api/admin/users/[id]/activities/route.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { UserService } from '@/lib/services/database-service'
|
||||
|
||||
const userService = new UserService()
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id: userId } = await params
|
||||
const { searchParams } = new URL(request.url)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const limit = parseInt(searchParams.get('limit') || '10')
|
||||
const search = searchParams.get('search') || ''
|
||||
const startDate = searchParams.get('startDate') || ''
|
||||
const endDate = searchParams.get('endDate') || ''
|
||||
const activityType = searchParams.get('activityType') || 'all'
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '用戶 ID 是必需的' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await userService.getUserActivities(userId, page, limit, {
|
||||
search,
|
||||
startDate,
|
||||
endDate,
|
||||
activityType
|
||||
})
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: result
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('獲取用戶活動記錄錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '獲取用戶活動記錄時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
@@ -3,71 +3,71 @@ import { UserService } from '@/lib/services/database-service'
|
||||
|
||||
const userService = new UserService()
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
try {
|
||||
const user = await userService.findById(params.id)
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json(
|
||||
{ error: '用戶不存在' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
// 獲取用戶統計
|
||||
const stats = await userService.getUserStatistics(params.id)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
user,
|
||||
stats
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('獲取用戶詳情錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ error: '獲取用戶詳情時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const updates = await request.json()
|
||||
|
||||
// 移除不允許更新的欄位
|
||||
delete updates.id
|
||||
delete updates.created_at
|
||||
delete updates.password_hash
|
||||
const { id: userId } = await params
|
||||
const body = await request.json()
|
||||
const { name, department, role, status } = body
|
||||
|
||||
const updatedUser = await userService.update(params.id, updates)
|
||||
|
||||
if (!updatedUser) {
|
||||
if (!userId) {
|
||||
return NextResponse.json(
|
||||
{ error: '用戶不存在或更新失敗' },
|
||||
{ status: 404 }
|
||||
{ success: false, error: '用戶 ID 是必需的' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用戶資料已更新',
|
||||
data: updatedUser
|
||||
if (!name || !department || !role || !status) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '請填寫所有必填欄位' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 驗證狀態值
|
||||
const validStatuses = ['active', 'inactive', 'invited']
|
||||
if (!validStatuses.includes(status)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '無效的狀態值' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 驗證角色值
|
||||
const validRoles = ['user', 'developer', 'admin']
|
||||
if (!validRoles.includes(role)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '無效的角色值' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const result = await userService.updateUser(userId, {
|
||||
name,
|
||||
department,
|
||||
role,
|
||||
status
|
||||
})
|
||||
|
||||
if (result.success) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用戶資料更新成功',
|
||||
data: result.user
|
||||
})
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: result.error },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新用戶錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ error: '更新用戶時發生錯誤' },
|
||||
{ success: false, error: '更新用戶時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
@@ -75,29 +75,37 @@ export async function PUT(
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
// 軟刪除:將 is_active 設為 false
|
||||
const result = await userService.update(params.id, { is_active: false })
|
||||
|
||||
if (!result) {
|
||||
const { id: userId } = await params
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json(
|
||||
{ error: '用戶不存在或刪除失敗' },
|
||||
{ status: 404 }
|
||||
{ success: false, error: '用戶 ID 是必需的' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用戶已刪除'
|
||||
})
|
||||
const result = await userService.deleteUser(userId)
|
||||
|
||||
if (result.success) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用戶刪除成功'
|
||||
})
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: result.error },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('刪除用戶錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ error: '刪除用戶時發生錯誤' },
|
||||
{ success: false, error: '刪除用戶時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
34
app/api/admin/users/[id]/stats/route.ts
Normal file
34
app/api/admin/users/[id]/stats/route.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { UserService } from '@/lib/services/database-service'
|
||||
|
||||
const userService = new UserService()
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
try {
|
||||
const { id: userId } = await params
|
||||
|
||||
if (!userId) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '用戶 ID 是必需的' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
const stats = await userService.getUserDetailedStats(userId)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: stats
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('獲取用戶統計數據錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '獲取用戶統計數據時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
@@ -3,6 +3,73 @@ import { UserService } from '@/lib/services/database-service'
|
||||
|
||||
const userService = new UserService()
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json()
|
||||
const { email, role } = body
|
||||
|
||||
if (!email || !role) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '請提供電子郵件和角色' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 驗證電子郵件格式
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(email)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '請輸入有效的電子郵件格式' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 驗證角色
|
||||
const validRoles = ['user', 'developer', 'admin']
|
||||
if (!validRoles.includes(role)) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '無效的角色值' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 檢查電子郵件是否已存在
|
||||
const existingUser = await userService.findByEmail(email)
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '此電子郵件已被使用' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 創建邀請用戶
|
||||
const result = await userService.createInvitedUser(email, role)
|
||||
|
||||
if (result.success) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '邀請用戶創建成功',
|
||||
data: {
|
||||
user: result.user,
|
||||
invitationLink: result.invitationLink
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: result.error },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('創建邀請用戶錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ success: false, error: '創建邀請用戶時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
@@ -14,7 +81,7 @@ export async function GET(request: NextRequest) {
|
||||
const status = searchParams.get('status') || ''
|
||||
|
||||
// 構建查詢條件
|
||||
let whereConditions = ['is_active = TRUE']
|
||||
let whereConditions = ['status IN ("active", "inactive", "invited")']
|
||||
let params: any[] = []
|
||||
|
||||
if (search) {
|
||||
@@ -34,9 +101,11 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
if (status && status !== 'all') {
|
||||
if (status === 'active') {
|
||||
whereConditions.push('last_login IS NOT NULL AND last_login >= DATE_SUB(NOW(), INTERVAL 30 DAY)')
|
||||
whereConditions.push('status = "active"')
|
||||
} else if (status === 'inactive') {
|
||||
whereConditions.push('last_login IS NULL OR last_login < DATE_SUB(NOW(), INTERVAL 30 DAY)')
|
||||
whereConditions.push('status = "inactive"')
|
||||
} else if (status === 'invited') {
|
||||
whereConditions.push('status = "invited"')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +123,21 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const stats = await userService.getUserStats()
|
||||
|
||||
// 映射用戶數據欄位,將資料庫欄位轉換為前端期望的欄位
|
||||
const mappedUsers = users.map(user => ({
|
||||
...user,
|
||||
joinDate: user.join_date ? new Date(user.join_date).toLocaleDateString('zh-TW') : '-',
|
||||
lastLogin: user.last_login ? new Date(user.last_login).toLocaleString('zh-TW') : '-',
|
||||
status: user.status, // 直接使用資料庫的 status 欄位
|
||||
totalApps: 0, // 暫時設為 0,後續可以從統計數據中獲取
|
||||
totalReviews: 0, // 暫時設為 0,後續可以從統計數據中獲取
|
||||
totalLikes: user.total_likes || 0
|
||||
}))
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
users,
|
||||
users: mappedUsers,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
@@ -65,12 +145,12 @@ export async function GET(request: NextRequest) {
|
||||
totalPages: Math.ceil(total / limit)
|
||||
},
|
||||
stats: {
|
||||
totalUsers: stats?.total_users || 0,
|
||||
activeUsers: stats?.active_users || 0,
|
||||
adminCount: stats?.admin_count || 0,
|
||||
developerCount: stats?.developer_count || 0,
|
||||
inactiveUsers: stats?.inactive_users || 0,
|
||||
newThisMonth: stats?.new_this_month || 0
|
||||
totalUsers: stats?.totalUsers || 0,
|
||||
activeUsers: stats?.activeUsers || 0,
|
||||
adminCount: stats?.adminCount || 0,
|
||||
developerCount: stats?.developerCount || 0,
|
||||
inactiveUsers: stats?.invitedUsers || 0, // 將 invitedUsers 映射為 inactiveUsers(待註冊)
|
||||
newThisMonth: stats?.newThisMonth || 0
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -84,51 +164,3 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { email, role } = await request.json()
|
||||
|
||||
if (!email || !role) {
|
||||
return NextResponse.json(
|
||||
{ error: '請提供電子郵件和角色' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 檢查郵箱是否已存在
|
||||
const existingUser = await userService.findByEmail(email)
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: '該電子郵件地址已被使用' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// 生成邀請 token
|
||||
const { v4: uuidv4 } = require('uuid')
|
||||
const invitationToken = uuidv4()
|
||||
|
||||
// 創建邀請記錄(這裡可以存儲到邀請表或臨時表)
|
||||
// 暫時返回邀請連結
|
||||
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
||||
const invitationLink = `${baseUrl}/register?token=${invitationToken}&email=${encodeURIComponent(email)}&role=${encodeURIComponent(role)}`
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '用戶邀請已創建',
|
||||
data: {
|
||||
invitationLink,
|
||||
token: invitationToken,
|
||||
email,
|
||||
role
|
||||
}
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('創建用戶邀請錯誤:', error)
|
||||
return NextResponse.json(
|
||||
{ error: '創建用戶邀請時發生錯誤' },
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -23,42 +23,69 @@ export async function POST(request: NextRequest) {
|
||||
)
|
||||
}
|
||||
|
||||
// 檢查用戶是否已存在
|
||||
const existingUser = await userService.findByEmail(email)
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: '該電子郵件已被註冊' },
|
||||
{ status: 409 }
|
||||
)
|
||||
}
|
||||
|
||||
// 加密密碼
|
||||
const saltRounds = 12
|
||||
const password_hash = await bcrypt.hash(password, saltRounds)
|
||||
|
||||
// 創建新用戶
|
||||
const newUser = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
email,
|
||||
password_hash,
|
||||
department,
|
||||
role: role as 'user' | 'developer' | 'admin',
|
||||
join_date: new Date().toISOString().split('T')[0],
|
||||
total_likes: 0,
|
||||
total_views: 0,
|
||||
is_active: true
|
||||
// 檢查是否為邀請用戶(狀態為 invited)
|
||||
const invitedUser = await userService.findInvitedUserByEmail(email)
|
||||
|
||||
if (invitedUser) {
|
||||
// 更新邀請用戶為正式用戶
|
||||
const updatedUser = await userService.completeInvitedUserRegistration(
|
||||
invitedUser.id,
|
||||
name,
|
||||
department,
|
||||
password_hash,
|
||||
role
|
||||
)
|
||||
|
||||
if (updatedUser.success) {
|
||||
const { password_hash: _, ...userWithoutPassword } = updatedUser.user
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: userWithoutPassword
|
||||
})
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{ error: updatedUser.error || '完成註冊失敗' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 檢查用戶是否已存在(活躍用戶)
|
||||
const existingUser = await userService.findByEmail(email)
|
||||
if (existingUser) {
|
||||
return NextResponse.json(
|
||||
{ error: '該電子郵件已被註冊' },
|
||||
{ status: 409 }
|
||||
)
|
||||
}
|
||||
|
||||
// 創建新用戶
|
||||
const newUser = {
|
||||
id: uuidv4(),
|
||||
name,
|
||||
email,
|
||||
password_hash,
|
||||
department,
|
||||
role: role as 'user' | 'developer' | 'admin',
|
||||
join_date: new Date().toISOString().split('T')[0],
|
||||
total_likes: 0,
|
||||
total_views: 0,
|
||||
status: 'active' as 'active' | 'inactive' | 'invited'
|
||||
}
|
||||
|
||||
const createdUser = await userService.create(newUser)
|
||||
|
||||
// 返回用戶信息(不包含密碼)
|
||||
const { password_hash: _, ...userWithoutPassword } = createdUser
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: userWithoutPassword
|
||||
})
|
||||
}
|
||||
|
||||
const createdUser = await userService.create(newUser)
|
||||
|
||||
// 返回用戶信息(不包含密碼)
|
||||
const { password_hash: _, ...userWithoutPassword } = createdUser
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
user: userWithoutPassword
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
console.error('註冊錯誤:', error)
|
||||
return NextResponse.json(
|
||||
|
@@ -130,6 +130,7 @@ export default function RegisterPage() {
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
department: formData.department,
|
||||
role: isInvitedUser ? invitedRole : 'user', // 如果是邀請用戶,使用邀請的角色
|
||||
})
|
||||
|
||||
if (success) {
|
||||
|
Reference in New Issue
Block a user