diff --git a/app/api/users/stats/route.ts b/app/api/users/stats/route.ts index e4090aa..48358bb 100644 --- a/app/api/users/stats/route.ts +++ b/app/api/users/stats/route.ts @@ -1,32 +1,48 @@ import { NextRequest, NextResponse } from 'next/server'; -import { requireAdmin } from '@/lib/auth'; +import { verifyToken } from '@/lib/auth'; import { db } from '@/lib/database'; export async function GET(request: NextRequest) { try { - await requireAdmin(request); - - // 基本用戶統計 - const total = await db.queryOne<{ count: number }>('SELECT COUNT(*) as count FROM users'); - const admin = await db.queryOne<{ count: number }>("SELECT COUNT(*) as count FROM users WHERE role = 'admin'"); - const developer = await db.queryOne<{ count: number }>("SELECT COUNT(*) as count FROM users WHERE role = 'developer'"); - const user = await db.queryOne<{ count: number }>("SELECT COUNT(*) as count FROM users WHERE role = 'user'"); - const today = await db.queryOne<{ count: number }>("SELECT COUNT(*) as count FROM users WHERE join_date = CURDATE()"); - - // 應用和評價統計 - const totalApps = await db.queryOne<{ count: number }>('SELECT COUNT(*) as count FROM apps'); - const totalReviews = await db.queryOne<{ count: number }>('SELECT COUNT(*) as count FROM judge_scores'); - + // 驗證管理員權限 + 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 stats = await db.queryOne(` + SELECT + COUNT(*) as total, + COUNT(CASE WHEN role = 'admin' THEN 1 END) as admin, + COUNT(CASE WHEN role = 'developer' THEN 1 END) as developer, + COUNT(CASE WHEN role = 'user' THEN 1 END) as user, + COUNT(CASE WHEN DATE(created_at) = CURDATE() THEN 1 END) as today + FROM users + `); + + // 優化:並行查詢應用和評價統計 + const [appsResult, reviewsResult] = await Promise.all([ + db.queryOne('SELECT COUNT(*) as count FROM apps'), + db.queryOne('SELECT COUNT(*) as count FROM judge_scores') + ]); + return NextResponse.json({ - total: total?.count || 0, - admin: admin?.count || 0, - developer: developer?.count || 0, - user: user?.count || 0, - today: today?.count || 0, - totalApps: totalApps?.count || 0, - totalReviews: totalReviews?.count || 0 + total: stats?.total || 0, + admin: stats?.admin || 0, + developer: stats?.developer || 0, + user: stats?.user || 0, + today: stats?.today || 0, + totalApps: appsResult?.count || 0, + totalReviews: reviewsResult?.count || 0 }); } catch (error) { - return NextResponse.json({ error: '內部伺服器錯誤', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 }); + console.error('Error fetching user stats:', error); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); } } \ No newline at end of file diff --git a/scripts/optimize-database.js b/scripts/optimize-database.js new file mode 100644 index 0000000..0b32b50 --- /dev/null +++ b/scripts/optimize-database.js @@ -0,0 +1,94 @@ +const mysql = require('mysql2/promise'); + +const dbConfig = { + host: process.env.DB_HOST || 'mysql.theaken.com', + user: process.env.DB_USER || 'AI_Platform', + password: process.env.DB_PASSWORD || 'Aa123456', + database: process.env.DB_NAME || 'db_AI_Platform', + port: process.env.DB_PORT || 33306 +}; + +async function optimizeDatabase() { + let connection; + try { + connection = await mysql.createConnection(dbConfig); + console.log('🔗 連接到資料庫...'); + + // 檢查並創建索引 + const indexes = [ + // users 表索引 + { table: 'users', column: 'role', name: 'idx_users_role' }, + { table: 'users', column: 'status', name: 'idx_users_status' }, + { table: 'users', column: 'created_at', name: 'idx_users_created_at' }, + { table: 'users', column: 'email', name: 'idx_users_email' }, + + // apps 表索引 + { table: 'apps', column: 'creator_id', name: 'idx_apps_creator_id' }, + { table: 'apps', column: 'created_at', name: 'idx_apps_created_at' }, + + // judge_scores 表索引 + { table: 'judge_scores', column: 'judge_id', name: 'idx_judge_scores_judge_id' }, + { table: 'judge_scores', column: 'created_at', name: 'idx_judge_scores_created_at' } + ]; + + for (const index of indexes) { + try { + // 檢查索引是否存在 + const [existingIndexes] = await connection.execute(` + SELECT INDEX_NAME + FROM INFORMATION_SCHEMA.STATISTICS + WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND INDEX_NAME = ? + `, [dbConfig.database, index.table, index.name]); + + if (existingIndexes.length === 0) { + // 創建索引 + await connection.execute(` + CREATE INDEX ${index.name} ON ${index.table} (${index.column}) + `); + console.log(`✅ 創建索引: ${index.name} on ${index.table}.${index.column}`); + } else { + console.log(`ℹ️ 索引已存在: ${index.name}`); + } + } catch (error) { + console.error(`❌ 創建索引失敗 ${index.name}:`, error.message); + } + } + + // 檢查表結構和統計信息 + console.log('\n📊 資料庫優化完成!'); + + // 顯示表統計信息 + const [tables] = await connection.execute(` + SELECT + TABLE_NAME, + TABLE_ROWS, + DATA_LENGTH, + INDEX_LENGTH + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_SCHEMA = ? + `, [dbConfig.database]); + + console.log('\n📋 表統計信息:'); + tables.forEach(table => { + const dataSize = Math.round(table.DATA_LENGTH / 1024); + const indexSize = Math.round(table.INDEX_LENGTH / 1024); + console.log(` ${table.TABLE_NAME}: ${table.TABLE_ROWS} 行, 資料 ${dataSize}KB, 索引 ${indexSize}KB`); + }); + + } catch (error) { + console.error('❌ 資料庫優化失敗:', error.message); + throw error; + } finally { + if (connection) await connection.end(); + } +} + +optimizeDatabase() + .then(() => { + console.log('✅ 資料庫優化完成!'); + process.exit(0); + }) + .catch(() => { + console.error('❌ 資料庫優化失敗!'); + process.exit(1); + }); \ No newline at end of file