diff --git a/app/api/admin/apps/[id]/stats/route.ts b/app/api/admin/apps/[id]/stats/route.ts
index 565dd1f..7dae865 100644
--- a/app/api/admin/apps/[id]/stats/route.ts
+++ b/app/api/admin/apps/[id]/stats/route.ts
@@ -22,12 +22,16 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
// 獲取使用統計
const usageStats = await appService.getAppUsageStats(appId)
+ // 獲取收藏數量
+ const favoritesCount = await appService.getAppFavoritesCount(appId)
+
return NextResponse.json({
success: true,
data: {
basic: {
views: app.views_count || 0,
likes: app.likes_count || 0,
+ favorites: favoritesCount,
rating: ratingStats.averageRating || 0,
reviewCount: ratingStats.totalRatings || 0
},
diff --git a/app/api/admin/apps/route.ts b/app/api/admin/apps/route.ts
index 9ba6264..1481953 100644
--- a/app/api/admin/apps/route.ts
+++ b/app/api/admin/apps/route.ts
@@ -24,7 +24,7 @@ export async function GET(request: NextRequest) {
})
// 獲取應用統計
- const stats = await appService.getAppStats()
+ const stats = await appService.getDashboardStats()
return NextResponse.json({
success: true,
diff --git a/app/api/admin/users/list/route.ts b/app/api/admin/users/list/route.ts
index 76f8952..7214bd2 100644
--- a/app/api/admin/users/list/route.ts
+++ b/app/api/admin/users/list/route.ts
@@ -5,26 +5,38 @@ const userService = new UserService()
export async function GET(request: NextRequest) {
try {
- // 獲取所有啟用狀態的用戶
+ // 獲取所有用戶(包括不同狀態)
const sql = `
- SELECT id, name, email, department, role
+ SELECT id, name, email, department, role, status, created_at, last_login
FROM users
- WHERE status = 'active'
ORDER BY name ASC
`;
const users = await userService.query(sql);
- return NextResponse.json({
- success: true,
- data: {
- users: users.map(user => ({
+ // 為每個用戶獲取統計數據
+ const usersWithStats = await Promise.all(
+ users.map(async (user) => {
+ const stats = await userService.getUserAppAndReviewStats(user.id);
+ return {
id: user.id,
name: user.name,
email: user.email,
department: user.department,
- role: user.role
- }))
+ role: user.role,
+ status: user.status,
+ createdAt: user.created_at,
+ lastLogin: user.last_login,
+ appCount: stats.appCount,
+ reviewCount: stats.reviewCount
+ };
+ })
+ );
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ users: usersWithStats
}
})
diff --git a/app/api/apps/[id]/favorite/route.ts b/app/api/apps/[id]/favorite/route.ts
new file mode 100644
index 0000000..26e1282
--- /dev/null
+++ b/app/api/apps/[id]/favorite/route.ts
@@ -0,0 +1,124 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { AppService } from '@/lib/services/database-service'
+
+const appService = new AppService()
+
+// 添加收藏
+export async function POST(request: NextRequest, { params }: { params: { id: string } }) {
+ try {
+ const { id: appId } = await params
+ const body = await request.json()
+ const { userId } = body
+
+ if (!userId) {
+ return NextResponse.json(
+ { success: false, error: '用戶ID不能為空' },
+ { status: 400 }
+ )
+ }
+
+ // 檢查應用是否存在
+ const app = await appService.getAppById(appId)
+ if (!app) {
+ return NextResponse.json(
+ { success: false, error: '應用不存在' },
+ { status: 404 }
+ )
+ }
+
+ // 添加收藏
+ const result = await appService.addFavorite(userId, appId)
+
+ if (result.success) {
+ return NextResponse.json({
+ success: true,
+ message: '收藏成功'
+ })
+ } else {
+ // 如果是重複收藏,返回 409 狀態碼
+ const statusCode = result.error === '已經收藏過此應用' ? 409 : 400
+ return NextResponse.json(
+ { success: false, error: result.error },
+ { status: statusCode }
+ )
+ }
+
+ } catch (error) {
+ console.error('添加收藏錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '添加收藏時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
+
+// 移除收藏
+export async function DELETE(request: NextRequest, { params }: { params: { id: string } }) {
+ try {
+ const { id: appId } = await params
+ const { searchParams } = new URL(request.url)
+ const userId = searchParams.get('userId')
+
+ if (!userId) {
+ return NextResponse.json(
+ { success: false, error: '用戶ID不能為空' },
+ { status: 400 }
+ )
+ }
+
+ // 移除收藏
+ const result = await appService.removeFavorite(userId, appId)
+
+ 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(
+ { success: false, error: '移除收藏時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
+
+// 檢查收藏狀態
+export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
+ try {
+ const { id: appId } = await params
+ const { searchParams } = new URL(request.url)
+ const userId = searchParams.get('userId')
+
+ if (!userId) {
+ return NextResponse.json(
+ { success: false, error: '用戶ID不能為空' },
+ { status: 400 }
+ )
+ }
+
+ // 檢查收藏狀態
+ const isFavorited = await appService.isFavorited(userId, appId)
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ isFavorited
+ }
+ })
+
+ } catch (error) {
+ console.error('檢查收藏狀態錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '檢查收藏狀態時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/apps/[id]/interactions/route.ts b/app/api/apps/[id]/interactions/route.ts
new file mode 100644
index 0000000..880424f
--- /dev/null
+++ b/app/api/apps/[id]/interactions/route.ts
@@ -0,0 +1,98 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { AppService } from '@/lib/services/database-service'
+
+const appService = new AppService()
+
+// 獲取應用的統計數據
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id: appId } = await params
+ const { searchParams } = new URL(request.url)
+ const userId = searchParams.get('userId')
+
+ // 獲取應用的統計數據
+ const stats = await appService.getAppStats(appId, userId || undefined)
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ likesCount: stats.likes_count || 0,
+ viewsCount: stats.views_count || 0,
+ rating: stats.average_rating || 0,
+ reviewsCount: stats.reviews_count || 0,
+ userLiked: stats.userLiked || false
+ }
+ })
+ } catch (error) {
+ console.error('獲取應用統計數據錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '獲取應用統計數據時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
+
+// 更新應用統計數據(按讚、觀看等)
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id: appId } = await params
+ const { action, userId } = await request.json()
+
+ if (!userId) {
+ return NextResponse.json(
+ { success: false, error: '需要用戶身份驗證' },
+ { status: 401 }
+ )
+ }
+
+ let result = false
+
+ switch (action) {
+ case 'like':
+ result = await appService.toggleAppLike(appId, userId)
+ break
+ case 'view':
+ result = await appService.incrementAppViews(appId, userId)
+ break
+ case 'favorite':
+ result = await appService.toggleAppFavorite(appId, userId)
+ break
+ default:
+ return NextResponse.json(
+ { success: false, error: '無效的操作類型' },
+ { status: 400 }
+ )
+ }
+
+ if (result) {
+ // 重新獲取更新後的統計數據
+ const stats = await appService.getAppStats(appId)
+ return NextResponse.json({
+ success: true,
+ data: {
+ likesCount: stats.likes_count || 0,
+ viewsCount: stats.views_count || 0,
+ rating: stats.average_rating || 0,
+ reviewsCount: stats.reviews_count || 0
+ }
+ })
+ } else {
+ return NextResponse.json(
+ { success: false, error: '操作失敗' },
+ { status: 500 }
+ )
+ }
+ } catch (error) {
+ console.error('更新應用統計數據錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '更新應用統計數據時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/apps/[id]/reviews/route.ts b/app/api/apps/[id]/reviews/route.ts
new file mode 100644
index 0000000..5a3db17
--- /dev/null
+++ b/app/api/apps/[id]/reviews/route.ts
@@ -0,0 +1,99 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { AppService } from '@/lib/services/database-service'
+
+const appService = new AppService()
+
+// 獲取應用的評論列表
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id: appId } = await params
+ const { searchParams } = new URL(request.url)
+ const limit = parseInt(searchParams.get('limit') || '5') // 預設5筆
+ const offset = parseInt(searchParams.get('offset') || '0')
+ const page = parseInt(searchParams.get('page') || '1')
+
+ const reviews = await appService.getAppReviews(appId, limit, offset)
+ const totalReviews = await appService.getAppReviewCount(appId)
+ const totalPages = Math.ceil(totalReviews / limit)
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ reviews,
+ pagination: {
+ page,
+ limit,
+ total: totalReviews,
+ totalPages,
+ hasNext: page < totalPages,
+ hasPrev: page > 1
+ }
+ }
+ })
+ } catch (error) {
+ console.error('獲取應用評論錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '獲取應用評論時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
+
+// 創建新的評論
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id: appId } = await params
+ const { userId, rating, comment, reviewId } = await request.json()
+
+ if (!userId || !rating || !comment) {
+ return NextResponse.json(
+ { success: false, error: '缺少必要參數' },
+ { status: 400 }
+ )
+ }
+
+ // 檢查評分範圍
+ if (rating < 1 || rating > 5) {
+ return NextResponse.json(
+ { success: false, error: '評分必須在 1-5 之間' },
+ { status: 400 }
+ )
+ }
+
+ let resultReviewId: string | null = null
+
+ if (reviewId) {
+ // 更新現有評論
+ resultReviewId = await appService.updateAppReview(reviewId, appId, userId, rating, comment)
+ } else {
+ // 創建新評論
+ resultReviewId = await appService.createAppReview(appId, userId, rating, comment)
+ }
+
+ if (resultReviewId) {
+ // 獲取更新後的評論列表
+ const reviews = await appService.getAppReviews(appId, 10, 0)
+ return NextResponse.json({
+ success: true,
+ data: { reviewId: resultReviewId, reviews }
+ })
+ } else {
+ return NextResponse.json(
+ { success: false, error: reviewId ? '更新評論失敗' : '創建評論失敗' },
+ { status: 500 }
+ )
+ }
+ } catch (error) {
+ console.error('處理應用評論錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '處理應用評論時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/apps/[id]/stats/route.ts b/app/api/apps/[id]/stats/route.ts
index 7874cd5..66e6a25 100644
--- a/app/api/apps/[id]/stats/route.ts
+++ b/app/api/apps/[id]/stats/route.ts
@@ -6,6 +6,11 @@ const appService = new AppService()
export async function GET(request: NextRequest, { params }: { params: { id: string } }) {
try {
const { id: appId } = await params
+ const { searchParams } = new URL(request.url)
+
+ // 獲取日期範圍參數
+ const startDate = searchParams.get('startDate')
+ const endDate = searchParams.get('endDate')
// 獲取應用基本統計
const app = await appService.getAppById(appId)
@@ -19,8 +24,11 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
// 獲取評分統計
const ratingStats = await appService.getAppRatingStats(appId)
- // 獲取使用趨勢數據
- const usageStats = await appService.getAppUsageStats(appId)
+ // 獲取使用趨勢數據(支援日期範圍)
+ const usageStats = await appService.getAppUsageStats(appId, startDate || undefined, endDate || undefined)
+
+ // 獲取收藏數量
+ const favoritesCount = await appService.getAppFavoritesCount(appId)
return NextResponse.json({
success: true,
@@ -28,6 +36,7 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
basic: {
views: app.views_count || 0,
likes: app.likes_count || 0,
+ favorites: favoritesCount,
rating: ratingStats.averageRating || 0,
reviewCount: ratingStats.totalRatings || 0
},
diff --git a/app/api/reviews/[id]/votes/route.ts b/app/api/reviews/[id]/votes/route.ts
new file mode 100644
index 0000000..c3a8880
--- /dev/null
+++ b/app/api/reviews/[id]/votes/route.ts
@@ -0,0 +1,69 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { AppService } from '@/lib/services/database-service'
+
+const appService = new AppService()
+
+// 獲取評論的投票統計
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id: reviewId } = await params
+ const { searchParams } = new URL(request.url)
+ const userId = searchParams.get('userId')
+
+ const votes = await appService.getReviewVotes(reviewId, userId || undefined)
+
+ return NextResponse.json({
+ success: true,
+ data: votes
+ })
+ } catch (error) {
+ console.error('獲取評論投票錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '獲取評論投票時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
+
+// 投票或取消投票
+export async function POST(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const { id: reviewId } = await params
+ const { userId, isHelpful } = await request.json()
+
+ if (!userId || typeof isHelpful !== 'boolean') {
+ return NextResponse.json(
+ { success: false, error: '缺少必要參數' },
+ { status: 400 }
+ )
+ }
+
+ const result = await appService.toggleReviewVote(reviewId, userId, isHelpful)
+
+ if (result.success) {
+ // 獲取更新後的投票統計
+ const votes = await appService.getReviewVotes(reviewId, userId)
+ return NextResponse.json({
+ success: true,
+ data: votes
+ })
+ } else {
+ return NextResponse.json(
+ { success: false, error: result.error || '投票失敗' },
+ { status: 500 }
+ )
+ }
+ } catch (error) {
+ console.error('投票評論錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '投票評論時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/user/activity/route.ts b/app/api/user/activity/route.ts
new file mode 100644
index 0000000..cb70fcd
--- /dev/null
+++ b/app/api/user/activity/route.ts
@@ -0,0 +1,79 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { AppService } from '@/lib/services/database-service'
+
+const appService = new AppService()
+
+// 獲取用戶活動記錄
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const userId = searchParams.get('userId')
+
+ if (!userId) {
+ return NextResponse.json(
+ { success: false, error: '用戶ID不能為空' },
+ { status: 400 }
+ )
+ }
+
+ // 獲取用戶最近使用的應用
+ const recentApps = await appService.getUserRecentApps(userId, 10)
+
+ // 獲取用戶統計數據
+ const userStats = await appService.getUserActivityStats(userId)
+
+ // 獲取類別使用統計
+ const categoryStats = await appService.getUserCategoryStats(userId)
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ recentApps,
+ userStats,
+ categoryStats
+ }
+ })
+
+ } catch (error) {
+ console.error('獲取用戶活動記錄錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '獲取活動記錄時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
+
+// 記錄用戶活動
+export async function POST(request: NextRequest) {
+ try {
+ const { userId, action, resourceType, resourceId, details } = await request.json()
+
+ if (!userId || !action || !resourceType) {
+ return NextResponse.json(
+ { success: false, error: '缺少必要參數' },
+ { status: 400 }
+ )
+ }
+
+ // 記錄活動到資料庫
+ const activityId = await appService.logUserActivity({
+ userId,
+ action,
+ resourceType,
+ resourceId,
+ details
+ })
+
+ return NextResponse.json({
+ success: true,
+ data: { activityId }
+ })
+
+ } catch (error) {
+ console.error('記錄用戶活動錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '記錄活動時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/user/favorites/route.ts b/app/api/user/favorites/route.ts
new file mode 100644
index 0000000..3a5c60a
--- /dev/null
+++ b/app/api/user/favorites/route.ts
@@ -0,0 +1,44 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { AppService } from '@/lib/services/database-service'
+
+const appService = new AppService()
+
+// 獲取用戶收藏列表
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const userId = searchParams.get('userId')
+ const page = parseInt(searchParams.get('page') || '1')
+ const limit = parseInt(searchParams.get('limit') || '20')
+
+ if (!userId) {
+ return NextResponse.json(
+ { success: false, error: '用戶ID不能為空' },
+ { status: 400 }
+ )
+ }
+
+ const offset = (page - 1) * limit
+ const result = await appService.getUserFavorites(userId, limit, offset)
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ apps: result.apps,
+ pagination: {
+ page,
+ limit,
+ total: result.total,
+ totalPages: Math.ceil(result.total / limit)
+ }
+ }
+ })
+
+ } catch (error) {
+ console.error('獲取用戶收藏列表錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '獲取收藏列表時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/api/user/interactions/route.ts b/app/api/user/interactions/route.ts
new file mode 100644
index 0000000..087caa8
--- /dev/null
+++ b/app/api/user/interactions/route.ts
@@ -0,0 +1,41 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { AppService } from '@/lib/services/database-service'
+
+const appService = new AppService()
+
+export async function GET(request: NextRequest) {
+ try {
+ const { searchParams } = new URL(request.url)
+ const userId = searchParams.get('userId')
+
+ if (!userId) {
+ return NextResponse.json(
+ { success: false, error: '缺少用戶ID' },
+ { status: 400 }
+ )
+ }
+
+ // 直接查詢收藏的應用ID(不依賴 apps 表的 is_active 狀態)
+ const favoriteAppIds = await appService.getUserFavoriteAppIds(userId)
+ console.log('用戶收藏的應用ID列表:', favoriteAppIds)
+
+ // 獲取用戶的按讚列表
+ const likedAppIds = await appService.getUserLikedApps(userId)
+ console.log('用戶按讚的應用ID列表:', likedAppIds)
+
+ return NextResponse.json({
+ success: true,
+ data: {
+ favorites: favoriteAppIds,
+ likes: likedAppIds
+ }
+ })
+
+ } catch (error) {
+ console.error('獲取用戶互動狀態錯誤:', error)
+ return NextResponse.json(
+ { success: false, error: '獲取用戶互動狀態時發生錯誤' },
+ { status: 500 }
+ )
+ }
+}
diff --git a/app/page.tsx b/app/page.tsx
index 9ed34b5..9fe3d15 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -198,6 +198,29 @@ export default function AIShowcasePlatform() {
setTotalPages(data.data.pagination.totalPages)
setTotalApps(data.data.pagination.total)
setDepartments(data.data.departments || [])
+
+ // 為每個應用載入統計數據
+ if (data.data.apps && data.data.apps.length > 0) {
+ const updatedApps = await Promise.all(
+ data.data.apps.map(async (app: any) => {
+ try {
+ const userId = user?.id
+ const statsResponse = await fetch(`/api/apps/${app.id}/interactions${userId ? `?userId=${userId}` : ''}`)
+ if (statsResponse.ok) {
+ const statsData = await statsResponse.json()
+ if (statsData.success) {
+ console.log(`載入應用 ${app.name} 的統計數據:`, statsData.data)
+ return { ...app, ...statsData.data }
+ }
+ }
+ } catch (error) {
+ console.error(`載入應用 ${app.name} 統計數據錯誤:`, error)
+ }
+ return app
+ })
+ )
+ setAiApps(updatedApps)
+ }
} else {
console.error('載入應用數據失敗:', data.error)
setAiApps([])
@@ -306,6 +329,13 @@ export default function AIShowcasePlatform() {
setShowAppDetail(true)
}
+ const handleAppDetailClose = async () => {
+ setShowAppDetail(false)
+ setSelectedApp(null)
+ // 重新載入應用數據以更新統計信息
+ await loadApps()
+ }
+
const handleSwitchToForgotPassword = () => {
setShowLogin(false)
setShowForgotPassword(true)
@@ -316,8 +346,8 @@ export default function AIShowcasePlatform() {
setShowLogin(true)
}
- const handleTryApp = (appId: string) => {
- incrementViewCount(appId)
+ const handleTryApp = async (appId: string) => {
+ await incrementViewCount(appId)
addToRecentApps(appId)
console.log(`Opening app ${appId}`)
}
@@ -972,17 +1002,18 @@ export default function AIShowcasePlatform() {
{currentApps.map((app) => {
const IconComponent = getIconComponent(app.icon || 'Bot')
- const likes = getAppLikes(app.id.toString())
- const views = getViewCount(app.id.toString())
- const rating = getAppRating(app.id.toString())
+ const likes = Number(app.likesCount) || 0
+ const views = Number(app.viewsCount) || 0
+ const rating = Number(app.rating) || 0
+ const reviewsCount = Number(app.reviewsCount) || 0
return (
-
-
+
+
@@ -991,6 +1022,9 @@ export default function AIShowcasePlatform() {
by {app.creator}
+
+
+
@@ -1007,7 +1041,12 @@ export default function AIShowcasePlatform() {
-
+
{views}
@@ -1016,9 +1055,12 @@ export default function AIShowcasePlatform() {
{rating.toFixed(1)}
+
+
+ {reviewsCount}
+
-
}
+ {selectedApp &&
}
{/* Favorites Dialog */}