完成管理者用戶功能新增、刪除、停用、查詢、編輯功能

This commit is contained in:
2025-08-05 11:36:19 +08:00
parent 92edcbe15f
commit 4e7b95d9fe
6 changed files with 725 additions and 154 deletions

View File

@@ -0,0 +1,50 @@
import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from '@/lib/auth'
import { db } from '@/lib/database'
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
try {
// 驗證管理員權限
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded || decoded.role !== 'admin') {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}
const userId = await params.id
// 檢查用戶是否存在
const user = await db.queryOne('SELECT id FROM users WHERE id = ?', [userId])
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
// 獲取用戶活動記錄
// 這裡可以根據實際需求查詢不同的活動表
// 目前先返回空數組,因為還沒有活動記錄表
const activities = []
// 格式化日期函數
const formatDate = (dateString: string | null) => {
if (!dateString) return "-";
const date = new Date(dateString);
return date.toLocaleString('zh-TW', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).replace(/\//g, '/');
};
return NextResponse.json(activities)
} catch (error) {
console.error('Error fetching user activity:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

180
app/api/users/[id]/route.ts Normal file
View File

@@ -0,0 +1,180 @@
import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from '@/lib/auth'
import { db } from '@/lib/database'
// GET /api/users/[id] - 查看用戶資料
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
try {
// 驗證管理員權限
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded || decoded.role !== 'admin') {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}
const userId = await params.id
// 查詢用戶詳細資料
const user = await db.queryOne(`
SELECT
u.id,
u.name,
u.email,
u.avatar,
u.department,
u.role,
u.status,
u.join_date,
u.total_likes,
u.total_views,
u.created_at,
u.updated_at,
COUNT(DISTINCT a.id) as total_apps,
COUNT(DISTINCT js.id) as total_reviews
FROM users u
LEFT JOIN apps a ON u.id = a.creator_id
LEFT JOIN judge_scores js ON u.id = js.judge_id
WHERE u.id = ?
GROUP BY u.id
`, [userId])
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
// 格式化日期函數
const formatDate = (dateString: string | null) => {
if (!dateString) return "-";
const date = new Date(dateString);
return date.toLocaleString('zh-TW', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
}).replace(/\//g, '/');
};
// 計算登入天數(基於最後更新時間)
const loginDays = user.updated_at ?
Math.floor((Date.now() - new Date(user.updated_at).getTime()) / (1000 * 60 * 60 * 24)) : 0;
return NextResponse.json({
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatar,
department: user.department,
role: user.role,
status: user.status || "active",
joinDate: formatDate(user.join_date),
lastLogin: formatDate(user.updated_at),
totalApps: user.total_apps || 0,
totalReviews: user.total_reviews || 0,
totalLikes: user.total_likes || 0,
loginDays: loginDays,
createdAt: formatDate(user.created_at),
updatedAt: formatDate(user.updated_at)
})
} catch (error) {
console.error('Error fetching user details:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
// PUT /api/users/[id] - 編輯用戶資料
export async function PUT(request: NextRequest, { params }: { params: { id: string } }) {
try {
// 驗證管理員權限
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded || decoded.role !== 'admin') {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}
const userId = await params.id
const body = await request.json()
const { name, email, department, role, status } = body
// 驗證必填欄位
if (!name || !email) {
return NextResponse.json({ error: 'Name and email are required' }, { status: 400 })
}
// 驗證電子郵件格式
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(email)) {
return NextResponse.json({ error: 'Invalid email format' }, { status: 400 })
}
// 檢查電子郵件唯一性(排除當前用戶)
const existingUser = await db.queryOne('SELECT id FROM users WHERE email = ? AND id != ?', [email, userId])
if (existingUser) {
return NextResponse.json({ error: 'Email already exists' }, { status: 409 })
}
// 更新用戶資料
await db.query(
'UPDATE users SET name = ?, email = ?, department = ?, role = ? WHERE id = ?',
[name, email, department, role, userId]
)
return NextResponse.json({ message: 'User updated successfully' })
} catch (error) {
console.error('Error updating user:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}
// DELETE /api/users/[id] - 刪除用戶
export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) {
try {
// 驗證管理員權限
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded || decoded.role !== 'admin') {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}
const userId = await params.id
// 檢查用戶是否存在
const user = await db.queryOne('SELECT id FROM users WHERE id = ?', [userId])
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
// 檢查是否為最後一個管理員
const adminCount = await db.queryOne('SELECT COUNT(*) as count FROM users WHERE role = "admin"')
const userRole = await db.queryOne('SELECT role FROM users WHERE id = ?', [userId])
if (adminCount?.count === 1 && userRole?.role === 'admin') {
return NextResponse.json({ error: 'Cannot delete the last admin user' }, { status: 400 })
}
// 級聯刪除相關資料
await db.query('DELETE FROM judge_scores WHERE judge_id = ?', [userId])
await db.query('DELETE FROM apps WHERE creator_id = ?', [userId])
// 刪除用戶
await db.query('DELETE FROM users WHERE id = ?', [userId])
return NextResponse.json({ message: 'User deleted successfully' })
} catch (error) {
console.error('Error deleting user:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

View File

@@ -0,0 +1,50 @@
import { NextRequest, NextResponse } from 'next/server'
import { verifyToken } from '@/lib/auth'
import { db } from '@/lib/database'
// PATCH /api/users/[id]/status - 停用/啟用用戶
export async function PATCH(request: NextRequest, { params }: { params: { id: string } }) {
try {
// 驗證管理員權限
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded || decoded.role !== 'admin') {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}
const userId = await params.id
const body = await request.json()
const { status } = body
// 驗證狀態值
if (!status || !['active', 'inactive'].includes(status)) {
return NextResponse.json({ error: 'Invalid status value' }, { status: 400 })
}
// 檢查用戶是否存在
const user = await db.queryOne('SELECT id, role FROM users WHERE id = ?', [userId])
if (!user) {
return NextResponse.json({ error: 'User not found' }, { status: 404 })
}
// 檢查是否為最後一個管理員
if (status === 'inactive' && user.role === 'admin') {
const adminCount = await db.queryOne('SELECT COUNT(*) as count FROM users WHERE role = "admin" AND status = "active"')
if (adminCount?.count <= 1) {
return NextResponse.json({ error: 'Cannot disable the last admin user' }, { status: 400 })
}
}
// 更新用戶狀態
await db.query('UPDATE users SET status = ? WHERE id = ?', [status, userId])
return NextResponse.json({ message: 'User status updated successfully' })
} catch (error) {
console.error('Error updating user status:', error)
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
}

View File

@@ -1,24 +1,31 @@
import { NextRequest, NextResponse } from 'next/server';
import { requireAdmin } from '@/lib/auth';
import { verifyToken } from '@/lib/auth';
import { db } from '@/lib/database';
import { logger } from '@/lib/logger';
export async function GET(request: NextRequest) {
const startTime = Date.now();
try {
// 驗證管理員權限
const admin = await requireAdmin(request);
const token = request.headers.get('authorization')?.replace('Bearer ', '')
if (!token) {
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}
const decoded = verifyToken(token)
if (!decoded || decoded.role !== 'admin') {
return NextResponse.json({ error: 'Admin access required' }, { status: 403 })
}
// 查詢參數
const { searchParams } = new URL(request.url);
const page = Math.max(1, parseInt(searchParams.get('page') || '1', 10));
const limit = Math.max(1, Math.min(100, parseInt(searchParams.get('limit') || '20', 10)));
const offset = (page - 1) * limit;
// 查詢用戶總數
const countResult = await db.queryOne<{ total: number }>('SELECT COUNT(*) as total FROM users');
// 優化:使用 COUNT(*) 查詢用戶總數
const countResult = await db.queryOne('SELECT COUNT(*) as total FROM users');
const total = countResult?.total || 0;
// 查詢用戶列表,包含應用和評價統計
// 優化:使用子查詢減少 JOIN 複雜度,提升查詢效能
const users = await db.query(`
SELECT
u.id,
@@ -27,20 +34,28 @@ export async function GET(request: NextRequest) {
u.avatar,
u.department,
u.role,
u.status,
u.join_date,
u.total_likes,
u.total_views,
u.created_at,
u.updated_at,
COUNT(DISTINCT a.id) as total_apps,
COUNT(DISTINCT js.id) as total_reviews
COALESCE(app_stats.total_apps, 0) as total_apps,
COALESCE(review_stats.total_reviews, 0) as total_reviews
FROM users u
LEFT JOIN apps a ON u.id = a.creator_id
LEFT JOIN judge_scores js ON u.id = js.judge_id
GROUP BY u.id
LEFT JOIN (
SELECT creator_id, COUNT(*) as total_apps
FROM apps
GROUP BY creator_id
) app_stats ON u.id = app_stats.creator_id
LEFT JOIN (
SELECT judge_id, COUNT(*) as total_reviews
FROM judge_scores
GROUP BY judge_id
) review_stats ON u.id = review_stats.judge_id
ORDER BY u.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`);
LIMIT ? OFFSET ?
`, [limit, offset]);
// 分頁資訊
const totalPages = Math.ceil(total / limit);
@@ -69,9 +84,9 @@ export async function GET(request: NextRequest) {
avatar: user.avatar,
department: user.department,
role: user.role,
status: "active", // 預設狀態為活躍
status: user.status || "active",
joinDate: formatDate(user.join_date),
lastLogin: formatDate(user.updated_at), // 使用 updated_at 作為最後登入時間
lastLogin: formatDate(user.updated_at),
totalApps: user.total_apps || 0,
totalReviews: user.total_reviews || 0,
totalLikes: user.total_likes || 0,
@@ -81,7 +96,7 @@ export async function GET(request: NextRequest) {
pagination: { page, limit, total, totalPages, hasNext, hasPrev }
});
} catch (error) {
logger.logError(error as Error, 'Users List API');
return NextResponse.json({ error: '內部伺服器錯誤', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 });
console.error('Error fetching users:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}