刪除不必要檔案

This commit is contained in:
2025-09-09 15:19:04 +08:00
parent 46bd9db2e3
commit 3369e3fc0f
86 changed files with 198 additions and 11011 deletions

View File

@@ -1,153 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { authenticateUser } from '@/lib/auth';
import { logger } from '@/lib/logger';
// POST /api/apps/[id]/favorite - 收藏應用程式
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '需要登入才能收藏' },
{ status: 401 }
);
}
const { id } = params;
// 檢查應用程式是否存在
const existingApp = await db.queryOne('SELECT * FROM apps WHERE id = ?', [id]);
if (!existingApp) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 檢查是否已經收藏過
const existingFavorite = await db.queryOne(
'SELECT * FROM user_favorites WHERE user_id = ? AND app_id = ?',
[user.id, id]
);
if (existingFavorite) {
return NextResponse.json(
{ error: '您已經收藏過此應用程式' },
{ status: 400 }
);
}
// 插入收藏記錄
const favoriteId = Date.now().toString(36) + Math.random().toString(36).substr(2);
await db.insert('user_favorites', {
id: favoriteId,
user_id: user.id,
app_id: id
});
// 記錄活動
logger.logActivity(user.id, 'app', id, 'favorite', {
appName: existingApp.name
});
const duration = Date.now() - startTime;
logger.logRequest('POST', `/api/apps/${id}/favorite`, 200, duration, user.id);
return NextResponse.json({
message: '收藏成功',
appId: id,
favoriteId
});
} catch (error) {
logger.logError(error as Error, 'Apps Favorite API');
const duration = Date.now() - startTime;
logger.logRequest('POST', `/api/apps/${params.id}/favorite`, 500, duration);
return NextResponse.json(
{ error: '收藏失敗' },
{ status: 500 }
);
}
}
// DELETE /api/apps/[id]/favorite - 取消收藏
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '需要登入才能取消收藏' },
{ status: 401 }
);
}
const { id } = params;
// 檢查應用程式是否存在
const existingApp = await db.queryOne('SELECT * FROM apps WHERE id = ?', [id]);
if (!existingApp) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 檢查是否已經收藏過
const existingFavorite = await db.queryOne(
'SELECT * FROM user_favorites WHERE user_id = ? AND app_id = ?',
[user.id, id]
);
if (!existingFavorite) {
return NextResponse.json(
{ error: '您還沒有收藏此應用程式' },
{ status: 400 }
);
}
// 刪除收藏記錄
await db.delete('user_favorites', {
user_id: user.id,
app_id: id
});
// 記錄活動
logger.logActivity(user.id, 'app', id, 'unfavorite', {
appName: existingApp.name
});
const duration = Date.now() - startTime;
logger.logRequest('DELETE', `/api/apps/${id}/favorite`, 200, duration, user.id);
return NextResponse.json({
message: '取消收藏成功',
appId: id
});
} catch (error) {
logger.logError(error as Error, 'Apps Unfavorite API');
const duration = Date.now() - startTime;
logger.logRequest('DELETE', `/api/apps/${params.id}/favorite`, 500, duration);
return NextResponse.json(
{ error: '取消收藏失敗' },
{ status: 500 }
);
}
}

View File

@@ -1,202 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { authenticateUser } from '@/lib/auth';
import { logger } from '@/lib/logger';
// POST /api/apps/[id]/like - 按讚應用程式
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '需要登入才能按讚' },
{ status: 401 }
);
}
const { id } = params;
// 檢查應用程式是否存在
const existingApp = await db.queryOne('SELECT * FROM apps WHERE id = ?', [id]);
if (!existingApp) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 檢查是否已經按讚過
const existingLike = await db.queryOne(
'SELECT * FROM user_likes WHERE user_id = ? AND app_id = ? AND DATE(liked_at) = CURDATE()',
[user.id, id]
);
if (existingLike) {
return NextResponse.json(
{ error: '您今天已經為此應用程式按讚過了' },
{ status: 400 }
);
}
// 開始事務
const connection = await db.beginTransaction();
try {
// 插入按讚記錄
const likeId = Date.now().toString(36) + Math.random().toString(36).substr(2);
await connection.execute(
'INSERT INTO user_likes (id, user_id, app_id, liked_at) VALUES (?, ?, ?, NOW())',
[likeId, user.id, id]
);
// 更新應用程式按讚數
await connection.execute(
'UPDATE apps SET likes_count = likes_count + 1 WHERE id = ?',
[id]
);
// 更新用戶總按讚數
await connection.execute(
'UPDATE users SET total_likes = total_likes + 1 WHERE id = ?',
[user.id]
);
// 提交事務
await db.commitTransaction(connection);
// 記錄活動
logger.logActivity(user.id, 'app', id, 'like', {
appName: existingApp.name
});
const duration = Date.now() - startTime;
logger.logRequest('POST', `/api/apps/${id}/like`, 200, duration, user.id);
return NextResponse.json({
message: '按讚成功',
appId: id,
likeId
});
} catch (error) {
// 回滾事務
await db.rollbackTransaction(connection);
throw error;
}
} catch (error) {
logger.logError(error as Error, 'Apps Like API');
const duration = Date.now() - startTime;
logger.logRequest('POST', `/api/apps/${params.id}/like`, 500, duration);
return NextResponse.json(
{ error: '按讚失敗' },
{ status: 500 }
);
}
}
// DELETE /api/apps/[id]/like - 取消按讚
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '需要登入才能取消按讚' },
{ status: 401 }
);
}
const { id } = params;
// 檢查應用程式是否存在
const existingApp = await db.queryOne('SELECT * FROM apps WHERE id = ?', [id]);
if (!existingApp) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 檢查是否已經按讚過
const existingLike = await db.queryOne(
'SELECT * FROM user_likes WHERE user_id = ? AND app_id = ? AND DATE(liked_at) = CURDATE()',
[user.id, id]
);
if (!existingLike) {
return NextResponse.json(
{ error: '您今天還沒有為此應用程式按讚' },
{ status: 400 }
);
}
// 開始事務
const connection = await db.beginTransaction();
try {
// 刪除按讚記錄
await connection.execute(
'DELETE FROM user_likes WHERE user_id = ? AND app_id = ? AND DATE(liked_at) = CURDATE()',
[user.id, id]
);
// 更新應用程式按讚數
await connection.execute(
'UPDATE apps SET likes_count = GREATEST(likes_count - 1, 0) WHERE id = ?',
[id]
);
// 更新用戶總按讚數
await connection.execute(
'UPDATE users SET total_likes = GREATEST(total_likes - 1, 0) WHERE id = ?',
[user.id]
);
// 提交事務
await db.commitTransaction(connection);
// 記錄活動
logger.logActivity(user.id, 'app', id, 'unlike', {
appName: existingApp.name
});
const duration = Date.now() - startTime;
logger.logRequest('DELETE', `/api/apps/${id}/like`, 200, duration, user.id);
return NextResponse.json({
message: '取消按讚成功',
appId: id
});
} catch (error) {
// 回滾事務
await db.rollbackTransaction(connection);
throw error;
}
} catch (error) {
logger.logError(error as Error, 'Apps Unlike API');
const duration = Date.now() - startTime;
logger.logRequest('DELETE', `/api/apps/${params.id}/like`, 500, duration);
return NextResponse.json(
{ error: '取消按讚失敗' },
{ status: 500 }
);
}
}

View File

@@ -1,380 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { authenticateUser, requireDeveloperOrAdmin } from '@/lib/auth';
import { logger } from '@/lib/logger';
import { AppUpdateRequest } from '@/types/app';
// GET /api/apps/[id] - 獲取單個應用程式詳細資料
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '需要登入才能查看應用程式' },
{ status: 401 }
);
}
const { id } = params;
// 查詢應用程式詳細資料
const sql = `
SELECT
a.*,
u.name as user_creator_name,
u.email as creator_email,
u.department as creator_department,
u.role as creator_role,
t.name as team_name,
t.department as team_department,
t.contact_email as team_contact_email,
t.leader_id as team_leader_id
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
LEFT JOIN teams t ON a.team_id = t.id
WHERE a.id = ?
`;
const app = await db.queryOne(sql, [id]);
if (!app) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 格式化回應資料
const formattedApp = {
id: app.id,
name: app.name,
description: app.description,
creatorId: app.creator_id,
teamId: app.team_id,
status: app.status,
type: app.type,
filePath: app.file_path,
techStack: app.tech_stack ? JSON.parse(app.tech_stack) : [],
tags: app.tags ? JSON.parse(app.tags) : [],
screenshots: app.screenshots ? JSON.parse(app.screenshots) : [],
demoUrl: app.demo_url,
githubUrl: app.github_url,
docsUrl: app.docs_url,
version: app.version,
icon: app.icon,
iconColor: app.icon_color,
likesCount: app.likes_count,
viewsCount: app.views_count,
rating: app.rating,
createdAt: app.created_at,
updatedAt: app.updated_at,
lastUpdated: app.last_updated,
department: app.department,
creator: {
id: app.creator_id,
name: app.creator_name || app.user_creator_name,
email: app.creator_email,
department: app.department || app.creator_department,
role: app.creator_role
},
team: app.team_id ? {
id: app.team_id,
name: app.team_name,
department: app.team_department,
contactEmail: app.team_contact_email,
leaderId: app.team_leader_id
} : undefined
};
// 增加瀏覽次數
await db.update(
'apps',
{ views_count: app.views_count + 1 },
{ id }
);
const duration = Date.now() - startTime;
logger.logRequest('GET', `/api/apps/${id}`, 200, duration, user.id);
return NextResponse.json(formattedApp);
} catch (error) {
logger.logError(error as Error, 'Apps API - GET by ID');
const duration = Date.now() - startTime;
logger.logRequest('GET', `/api/apps/${params.id}`, 500, duration);
return NextResponse.json(
{ error: '獲取應用程式詳細資料失敗' },
{ status: 500 }
);
}
}
// PUT /api/apps/[id] - 更新應用程式
export async function PUT(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await requireDeveloperOrAdmin(request);
const { id } = params;
const body = await request.json();
const {
name,
description,
type,
teamId,
status,
techStack,
tags,
screenshots,
demoUrl,
githubUrl,
docsUrl,
version,
icon,
iconColor
}: AppUpdateRequest = body;
// 檢查應用程式是否存在
const existingApp = await db.queryOne('SELECT * FROM apps WHERE id = ?', [id]);
if (!existingApp) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 檢查權限:只有創建者或管理員可以編輯
if (existingApp.creator_id !== user.id && user.role !== 'admin') {
return NextResponse.json(
{ error: '您沒有權限編輯此應用程式' },
{ status: 403 }
);
}
// 驗證更新資料
const updateData: any = {};
if (name !== undefined) {
if (name.length < 2 || name.length > 200) {
return NextResponse.json(
{ error: '應用程式名稱長度必須在 2-200 個字符之間' },
{ status: 400 }
);
}
updateData.name = name;
}
if (description !== undefined) {
if (description.length < 10) {
return NextResponse.json(
{ error: '應用程式描述至少需要 10 個字符' },
{ status: 400 }
);
}
updateData.description = description;
}
if (type !== undefined) {
const validTypes = [
'productivity', 'ai_model', 'automation', 'data_analysis', 'educational',
'healthcare', 'finance', 'iot_device', 'blockchain', 'ar_vr',
'machine_learning', 'computer_vision', 'nlp', 'robotics', 'cybersecurity',
'cloud_service', 'other'
];
if (!validTypes.includes(type)) {
return NextResponse.json(
{ error: '無效的應用程式類型' },
{ status: 400 }
);
}
updateData.type = type;
}
if (status !== undefined) {
const validStatuses = ['draft', 'submitted', 'under_review', 'approved', 'rejected', 'published'];
if (!validStatuses.includes(status)) {
return NextResponse.json(
{ error: '無效的應用程式狀態' },
{ status: 400 }
);
}
updateData.status = status;
}
if (teamId !== undefined) {
// 如果指定了團隊,驗證團隊存在且用戶是團隊成員
if (teamId) {
const teamMember = await db.queryOne(
'SELECT * FROM team_members WHERE team_id = ? AND user_id = ?',
[teamId, user.id]
);
if (!teamMember) {
return NextResponse.json(
{ error: '您不是該團隊的成員,無法將應用程式分配給該團隊' },
{ status: 403 }
);
}
}
updateData.team_id = teamId || null;
}
if (techStack !== undefined) {
updateData.tech_stack = techStack ? JSON.stringify(techStack) : null;
}
if (tags !== undefined) {
updateData.tags = tags ? JSON.stringify(tags) : null;
}
if (screenshots !== undefined) {
updateData.screenshots = screenshots ? JSON.stringify(screenshots) : null;
}
if (demoUrl !== undefined) {
updateData.demo_url = demoUrl || null;
}
if (githubUrl !== undefined) {
updateData.github_url = githubUrl || null;
}
if (docsUrl !== undefined) {
updateData.docs_url = docsUrl || null;
}
if (version !== undefined) {
updateData.version = version;
}
if (icon !== undefined) {
updateData.icon = icon;
}
if (iconColor !== undefined) {
updateData.icon_color = iconColor;
}
// 更新應用程式
if (Object.keys(updateData).length > 0) {
await db.update('apps', updateData, { id });
// 記錄活動
logger.logActivity(user.id, 'app', id, 'update', updateData);
}
const duration = Date.now() - startTime;
logger.logRequest('PUT', `/api/apps/${id}`, 200, duration, user.id);
return NextResponse.json({
message: '應用程式更新成功',
appId: id
});
} catch (error) {
logger.logError(error as Error, 'Apps API - PUT');
const duration = Date.now() - startTime;
logger.logRequest('PUT', `/api/apps/${params.id}`, 500, duration);
return NextResponse.json(
{ error: '更新應用程式失敗' },
{ status: 500 }
);
}
}
// DELETE /api/apps/[id] - 刪除應用程式
export async function DELETE(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await requireDeveloperOrAdmin(request);
const { id } = params;
// 檢查應用程式是否存在
const existingApp = await db.queryOne('SELECT * FROM apps WHERE id = ?', [id]);
if (!existingApp) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 檢查權限:只有創建者或管理員可以刪除
if (existingApp.creator_id !== user.id && user.role !== 'admin') {
return NextResponse.json(
{ error: '您沒有權限刪除此應用程式' },
{ status: 403 }
);
}
// 開始事務
const connection = await db.beginTransaction();
try {
// 刪除相關的按讚記錄
await connection.execute('DELETE FROM user_likes WHERE app_id = ?', [id]);
// 刪除相關的收藏記錄
await connection.execute('DELETE FROM user_favorites WHERE app_id = ?', [id]);
// 刪除相關的評分記錄
await connection.execute('DELETE FROM judge_scores WHERE app_id = ?', [id]);
// 刪除應用程式
await connection.execute('DELETE FROM apps WHERE id = ?', [id]);
// 提交事務
await db.commitTransaction(connection);
// 記錄活動
logger.logActivity(user.id, 'app', id, 'delete', {
name: existingApp.name,
type: existingApp.type
});
const duration = Date.now() - startTime;
logger.logRequest('DELETE', `/api/apps/${id}`, 200, duration, user.id);
return NextResponse.json({
message: '應用程式刪除成功',
appId: id
});
} catch (error) {
// 回滾事務
await db.rollbackTransaction(connection);
throw error;
}
} catch (error) {
logger.logError(error as Error, 'Apps API - DELETE');
const duration = Date.now() - startTime;
logger.logRequest('DELETE', `/api/apps/${params.id}`, 500, duration);
return NextResponse.json(
{ error: '刪除應用程式失敗' },
{ status: 500 }
);
}
}

View File

@@ -1,151 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { requireDeveloperOrAdmin } from '@/lib/auth';
import { logger } from '@/lib/logger';
import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { existsSync } from 'fs';
// POST /api/apps/[id]/upload - 上傳應用程式檔案
export async function POST(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await requireDeveloperOrAdmin(request);
const { id } = params;
// 檢查應用程式是否存在
const existingApp = await db.queryOne('SELECT * FROM apps WHERE id = ?', [id]);
if (!existingApp) {
return NextResponse.json(
{ error: '應用程式不存在' },
{ status: 404 }
);
}
// 檢查權限:只有創建者或管理員可以上傳檔案
if (existingApp.creator_id !== user.id && user.role !== 'admin') {
return NextResponse.json(
{ error: '您沒有權限為此應用程式上傳檔案' },
{ status: 403 }
);
}
// 解析 FormData
const formData = await request.formData();
const file = formData.get('file') as File;
const type = formData.get('type') as string;
if (!file) {
return NextResponse.json(
{ error: '請選擇要上傳的檔案' },
{ status: 400 }
);
}
// 驗證檔案類型
const validTypes = ['screenshot', 'document', 'source_code'];
if (!validTypes.includes(type)) {
return NextResponse.json(
{ error: '無效的檔案類型' },
{ status: 400 }
);
}
// 驗證檔案大小 (最大 10MB)
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
return NextResponse.json(
{ error: '檔案大小不能超過 10MB' },
{ status: 400 }
);
}
// 驗證檔案格式
const allowedExtensions = {
screenshot: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
document: ['.pdf', '.doc', '.docx', '.txt', '.md'],
source_code: ['.zip', '.rar', '.7z', '.tar.gz']
};
const fileName = file.name.toLowerCase();
const fileExtension = fileName.substring(fileName.lastIndexOf('.'));
const allowedExts = allowedExtensions[type as keyof typeof allowedExtensions];
if (!allowedExts.includes(fileExtension)) {
return NextResponse.json(
{ error: `此檔案類型不支援 ${type} 上傳` },
{ status: 400 }
);
}
// 創建上傳目錄
const uploadDir = join(process.cwd(), 'public', 'uploads', 'apps', id);
if (!existsSync(uploadDir)) {
await mkdir(uploadDir, { recursive: true });
}
// 生成唯一檔案名
const timestamp = Date.now();
const uniqueFileName = `${type}_${timestamp}_${file.name}`;
const filePath = join(uploadDir, uniqueFileName);
const relativePath = `/uploads/apps/${id}/${uniqueFileName}`;
// 將檔案寫入磁碟
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes);
await writeFile(filePath, buffer);
// 更新應用程式資料
let updateData: any = {};
if (type === 'screenshot') {
// 獲取現有的截圖列表
const currentScreenshots = existingApp.screenshots ? JSON.parse(existingApp.screenshots) : [];
currentScreenshots.push(relativePath);
updateData.screenshots = JSON.stringify(currentScreenshots);
} else if (type === 'source_code') {
// 更新檔案路徑
updateData.file_path = relativePath;
}
if (Object.keys(updateData).length > 0) {
await db.update('apps', updateData, { id });
}
// 記錄活動
logger.logActivity(user.id, 'app', id, 'upload_file', {
fileName: file.name,
fileType: type,
fileSize: file.size,
filePath: relativePath
});
const duration = Date.now() - startTime;
logger.logRequest('POST', `/api/apps/${id}/upload`, 200, duration, user.id);
return NextResponse.json({
message: '檔案上傳成功',
fileName: file.name,
fileType: type,
filePath: relativePath,
fileSize: file.size
});
} catch (error) {
logger.logError(error as Error, 'Apps Upload API');
const duration = Date.now() - startTime;
logger.logRequest('POST', `/api/apps/${params.id}/upload`, 500, duration);
return NextResponse.json(
{ error: '檔案上傳失敗' },
{ status: 500 }
);
}
}

View File

@@ -1,352 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { authenticateUser, requireDeveloperOrAdmin } from '@/lib/auth';
import { logger } from '@/lib/logger';
import { AppSearchParams, AppCreateRequest } from '@/types/app';
// GET /api/apps - 獲取應用程式列表
export async function GET(request: NextRequest) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '需要登入才能查看應用程式' },
{ status: 401 }
);
}
// 解析查詢參數
const { searchParams } = new URL(request.url);
const search = searchParams.get('search') || '';
const type = searchParams.get('type') || '';
const status = searchParams.get('status') || '';
const creatorId = searchParams.get('creatorId') || '';
const teamId = searchParams.get('teamId') || '';
const page = parseInt(searchParams.get('page') || '1');
const limit = parseInt(searchParams.get('limit') || '10');
// 確保參數是數字類型
const limitNum = Number(limit);
const offsetNum = Number((page - 1) * limit);
const sortBy = searchParams.get('sortBy') || 'created_at';
const sortOrder = searchParams.get('sortOrder') || 'desc';
// 構建查詢條件
const conditions: string[] = [];
const params: any[] = [];
if (search) {
conditions.push('(a.name LIKE ? OR a.description LIKE ? OR u.name LIKE ?)');
const searchTerm = `%${search}%`;
params.push(searchTerm, searchTerm, searchTerm);
}
if (type) {
conditions.push('a.type = ?');
params.push(type);
}
if (status) {
conditions.push('a.status = ?');
params.push(status);
}
if (creatorId) {
conditions.push('a.creator_id = ?');
params.push(creatorId);
}
if (teamId) {
conditions.push('a.team_id = ?');
params.push(teamId);
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
// 計算總數
const countSql = `
SELECT COUNT(*) as total
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
${whereClause}
`;
const totalResults = await db.query<{ total: number }>(countSql, params);
const total = totalResults.length > 0 ? totalResults[0].total : 0;
// 計算分頁
const totalPages = Math.ceil(total / limit);
// 計算各狀態的統計
const statsSql = `
SELECT a.status, COUNT(*) as count
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
${whereClause}
GROUP BY a.status
`;
const statsResults = await db.query(statsSql, params);
const stats = {
published: 0,
pending: 0,
draft: 0,
rejected: 0
};
statsResults.forEach((row: any) => {
if (stats.hasOwnProperty(row.status)) {
stats[row.status] = row.count;
}
});
// 構建排序
const validSortFields = ['name', 'created_at', 'rating', 'likes_count', 'views_count'];
const validSortOrders = ['asc', 'desc'];
const finalSortBy = validSortFields.includes(sortBy) ? sortBy : 'created_at';
const finalSortOrder = validSortOrders.includes(sortOrder) ? sortOrder : 'desc';
// 查詢應用程式列表
const sql = `
SELECT
a.*,
u.name as user_creator_name,
u.email as user_creator_email,
u.department as user_creator_department,
u.role as creator_role
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
${whereClause}
ORDER BY a.created_at DESC
LIMIT ${limitNum} OFFSET ${offsetNum}
`;
const apps = await db.query(sql, params);
// 格式化回應資料
const formattedApps = apps.map((app: any) => ({
id: app.id,
name: app.name,
description: app.description,
creatorId: app.creator_id,
teamId: app.team_id,
status: app.status,
type: app.type,
filePath: app.file_path,
techStack: app.tech_stack ? JSON.parse(app.tech_stack) : [],
tags: app.tags ? JSON.parse(app.tags) : [],
screenshots: app.screenshots ? JSON.parse(app.screenshots) : [],
demoUrl: app.demo_url,
githubUrl: app.github_url,
docsUrl: app.docs_url,
version: app.version,
icon: app.icon,
iconColor: app.icon_color,
likesCount: app.likes_count,
viewsCount: app.views_count,
rating: app.rating,
createdAt: app.created_at,
updatedAt: app.updated_at,
lastUpdated: app.last_updated,
department: app.department,
creator: {
id: app.creator_id,
name: app.creator_name || app.user_creator_name,
email: app.user_creator_email,
department: app.department || app.user_creator_department,
role: app.creator_role
},
team: app.team_id ? {
id: app.team_id,
name: app.team_name,
department: app.team_department,
contactEmail: app.team_contact_email
} : undefined
}));
const duration = Date.now() - startTime;
logger.logRequest('GET', '/api/apps', 200, duration, user.id);
return NextResponse.json({
apps: formattedApps,
pagination: {
page,
limit,
total,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
},
stats
});
} catch (error) {
logger.logError(error as Error, 'Apps API - GET');
const duration = Date.now() - startTime;
logger.logRequest('GET', '/api/apps', 500, duration);
console.error('詳細錯誤信息:', error);
return NextResponse.json(
{
error: '獲取應用程式列表失敗',
details: error instanceof Error ? error.message : '未知錯誤'
},
{ status: 500 }
);
}
}
// POST /api/apps - 創建新應用程式
export async function POST(request: NextRequest) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await requireDeveloperOrAdmin(request);
const body = await request.json();
const {
name,
description,
type,
teamId,
techStack,
tags,
demoUrl,
githubUrl,
docsUrl,
version = '1.0.0',
creator,
department,
icon = 'Bot',
iconColor = 'from-blue-500 to-purple-500'
}: AppCreateRequest = body;
// 驗證必填欄位
if (!name || !description || !type) {
return NextResponse.json(
{ error: '請提供應用程式名稱、描述和類型' },
{ status: 400 }
);
}
// 驗證應用程式名稱長度
if (name.length < 2 || name.length > 200) {
return NextResponse.json(
{ error: '應用程式名稱長度必須在 2-200 個字符之間' },
{ status: 400 }
);
}
// 驗證描述長度
if (description.length < 10) {
return NextResponse.json(
{ error: '應用程式描述至少需要 10 個字符' },
{ status: 400 }
);
}
// 驗證類型
const validTypes = [
'productivity', 'ai_model', 'automation', 'data_analysis', 'educational',
'healthcare', 'finance', 'iot_device', 'blockchain', 'ar_vr',
'machine_learning', 'computer_vision', 'nlp', 'robotics', 'cybersecurity',
'cloud_service', 'other'
];
if (!validTypes.includes(type)) {
return NextResponse.json(
{ error: '無效的應用程式類型' },
{ status: 400 }
);
}
// 如果指定了團隊,驗證團隊存在且用戶是團隊成員
if (teamId) {
const teamMember = await db.queryOne(
'SELECT * FROM team_members WHERE team_id = ? AND user_id = ?',
[teamId, user.id]
);
if (!teamMember) {
return NextResponse.json(
{ error: '您不是該團隊的成員,無法為該團隊創建應用程式' },
{ status: 403 }
);
}
}
// 生成應用程式 ID
const appId = Date.now().toString(36) + Math.random().toString(36).substr(2);
// 準備插入資料
const appData = {
id: appId,
name,
description,
creator_id: user.id,
team_id: teamId || null,
type,
tech_stack: techStack ? JSON.stringify(techStack) : null,
tags: tags ? JSON.stringify(tags) : null,
demo_url: demoUrl || null,
github_url: githubUrl || null,
docs_url: docsUrl || null,
version,
status: 'draft',
icon: icon || 'Bot',
icon_color: iconColor || 'from-blue-500 to-purple-500',
department: department || user.department || 'HQBU',
creator_name: creator || user.name || '',
creator_email: user.email || ''
};
// 插入應用程式
await db.insert('apps', appData);
// 記錄活動
logger.logActivity(user.id, 'app', appId, 'create', {
name,
type,
teamId
});
const duration = Date.now() - startTime;
logger.logRequest('POST', '/api/apps', 201, duration, user.id);
return NextResponse.json({
message: '應用程式創建成功',
appId,
app: {
id: appId,
name,
description,
type,
status: 'draft',
creatorId: user.id,
teamId,
version
}
}, { status: 201 });
} catch (error) {
logger.logError(error as Error, 'Apps API - POST');
const duration = Date.now() - startTime;
logger.logRequest('POST', '/api/apps', 500, duration);
console.error('詳細錯誤信息:', error);
return NextResponse.json(
{
error: '創建應用程式失敗',
details: error instanceof Error ? error.message : '未知錯誤'
},
{ status: 500 }
);
}
}

View File

@@ -1,169 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { authenticateUser } from '@/lib/auth';
import { logger } from '@/lib/logger';
import { AppStats } from '@/types/app';
// GET /api/apps/stats - 獲取應用程式統計資料
export async function GET(request: NextRequest) {
const startTime = Date.now();
try {
// 驗證用戶權限
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '需要登入才能查看統計資料' },
{ status: 401 }
);
}
// 查詢總體統計
const totalStats = await db.queryOne<{ total: number }>('SELECT COUNT(*) as total FROM apps');
const publishedStats = await db.queryOne<{ published: number }>('SELECT COUNT(*) as published FROM apps WHERE status = "published"');
const pendingReviewStats = await db.queryOne<{ pending: number }>('SELECT COUNT(*) as pending FROM apps WHERE status = "submitted" OR status = "under_review"');
const draftStats = await db.queryOne<{ draft: number }>('SELECT COUNT(*) as draft FROM apps WHERE status = "draft"');
const approvedStats = await db.queryOne<{ approved: number }>('SELECT COUNT(*) as approved FROM apps WHERE status = "approved"');
const rejectedStats = await db.queryOne<{ rejected: number }>('SELECT COUNT(*) as rejected FROM apps WHERE status = "rejected"');
// 查詢按類型統計
const typeStats = await db.query(`
SELECT type, COUNT(*) as count
FROM apps
GROUP BY type
`);
// 查詢按狀態統計
const statusStats = await db.query(`
SELECT status, COUNT(*) as count
FROM apps
GROUP BY status
`);
// 查詢按創建者統計
const creatorStats = await db.query(`
SELECT
u.name as creator_name,
COUNT(a.id) as app_count,
SUM(a.likes_count) as total_likes,
SUM(a.views_count) as total_views,
AVG(a.rating) as avg_rating
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
GROUP BY a.creator_id, u.name
ORDER BY app_count DESC
LIMIT 10
`);
// 查詢按團隊統計
const teamStats = await db.query(`
SELECT
t.name as team_name,
COUNT(a.id) as app_count,
SUM(a.likes_count) as total_likes,
SUM(a.views_count) as total_views,
AVG(a.rating) as avg_rating
FROM apps a
LEFT JOIN teams t ON a.team_id = t.id
WHERE a.team_id IS NOT NULL
GROUP BY a.team_id, t.name
ORDER BY app_count DESC
LIMIT 10
`);
// 查詢最近創建的應用程式
const recentApps = await db.query(`
SELECT
a.id,
a.name,
a.type,
a.status,
a.created_at,
u.name as creator_name
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
ORDER BY a.created_at DESC
LIMIT 5
`);
// 查詢最受歡迎的應用程式
const popularApps = await db.query(`
SELECT
a.id,
a.name,
a.type,
a.likes_count,
a.views_count,
a.rating,
u.name as creator_name
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
ORDER BY a.likes_count DESC, a.views_count DESC
LIMIT 5
`);
// 查詢評分最高的應用程式
const topRatedApps = await db.query(`
SELECT
a.id,
a.name,
a.type,
a.rating,
a.likes_count,
a.views_count,
u.name as creator_name
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
WHERE a.rating > 0
ORDER BY a.rating DESC
LIMIT 5
`);
// 格式化按類型統計
const byType: Record<string, number> = {};
typeStats.forEach((stat: any) => {
byType[stat.type] = stat.count;
});
// 格式化按狀態統計
const byStatus: Record<string, number> = {};
statusStats.forEach((stat: any) => {
byStatus[stat.status] = stat.count;
});
// 構建統計回應
const stats: AppStats = {
total: totalStats?.total || 0,
published: publishedStats?.published || 0,
pendingReview: pendingReviewStats?.pending || 0,
draft: draftStats?.draft || 0,
approved: approvedStats?.approved || 0,
rejected: rejectedStats?.rejected || 0,
byType,
byStatus
};
const duration = Date.now() - startTime;
logger.logRequest('GET', '/api/apps/stats', 200, duration, user.id);
return NextResponse.json({
stats,
creatorStats,
teamStats,
recentApps,
popularApps,
topRatedApps
});
} catch (error) {
logger.logError(error as Error, 'Apps Stats API');
const duration = Date.now() - startTime;
logger.logRequest('GET', '/api/apps/stats', 500, duration);
return NextResponse.json(
{ error: '獲取應用程式統計資料失敗' },
{ status: 500 }
);
}
}

View File

@@ -1,47 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { authenticateUser } from '@/lib/auth';
import { logger } from '@/lib/logger';
export async function GET(request: NextRequest) {
const startTime = Date.now();
try {
// 驗證用戶
const user = await authenticateUser(request);
if (!user) {
return NextResponse.json(
{ error: '未授權訪問' },
{ status: 401 }
);
}
const duration = Date.now() - startTime;
logger.logRequest('GET', '/api/auth/me', 200, duration, user.id);
return NextResponse.json({
user: {
id: user.id,
name: user.name,
email: user.email,
avatar: user.avatar,
department: user.department,
role: user.role,
joinDate: user.joinDate,
totalLikes: user.totalLikes,
totalViews: user.totalViews
}
});
} catch (error) {
logger.logError(error as Error, 'Get Current User API');
const duration = Date.now() - startTime;
logger.logRequest('GET', '/api/auth/me', 500, duration);
return NextResponse.json(
{ error: '內部伺服器錯誤' },
{ status: 500 }
);
}
}

View File

@@ -1,19 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
import { hashPassword } from '@/lib/auth';
import { codeMap } from '../request/route';
export async function POST(request: NextRequest) {
try {
const { email, code, newPassword } = await request.json();
if (!email || !code || !newPassword) return NextResponse.json({ error: '缺少參數' }, { status: 400 });
const validCode = codeMap.get(email);
if (!validCode || validCode !== code) return NextResponse.json({ error: '驗證碼錯誤' }, { status: 400 });
const passwordHash = await hashPassword(newPassword);
await db.update('users', { password_hash: passwordHash }, { email });
codeMap.delete(email);
return NextResponse.json({ message: '密碼重設成功' });
} catch (error) {
return NextResponse.json({ error: '內部伺服器錯誤', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 });
}
}

View File

@@ -1,20 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
const codeMap = new Map();
export async function POST(request: NextRequest) {
try {
const { email } = await request.json();
if (!email) return NextResponse.json({ error: '請提供 email' }, { status: 400 });
const user = await db.queryOne('SELECT id FROM users WHERE email = ?', [email]);
if (!user) return NextResponse.json({ error: '用戶不存在' }, { status: 404 });
const code = Math.floor(100000 + Math.random() * 900000).toString();
codeMap.set(email, code);
// 實際應發送 email這裡直接回傳
return NextResponse.json({ message: '驗證碼已產生', code });
} catch (error) {
return NextResponse.json({ error: '內部伺服器錯誤', details: error instanceof Error ? error.message : 'Unknown error' }, { status: 500 });
}
}
export { codeMap };

View File

@@ -1,35 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { db } from '@/lib/database';
export async function GET(request: NextRequest) {
try {
// 健康檢查
const isHealthy = await db.healthCheck();
if (!isHealthy) {
return NextResponse.json(
{ error: 'Database connection failed' },
{ status: 503 }
);
}
// 獲取基本統計
const stats = await db.getDatabaseStats();
return NextResponse.json({
message: 'AI Platform API is running',
version: '1.0.0',
timestamp: new Date().toISOString(),
database: {
status: 'connected',
stats
}
});
} catch (error) {
console.error('API Health Check Error:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}

View File

@@ -1,50 +0,0 @@
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 })
}
}

View File

@@ -1,180 +0,0 @@
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

@@ -1,50 +0,0 @@
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,102 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { verifyToken } from '@/lib/auth';
import { db } from '@/lib/database';
export async function GET(request: NextRequest) {
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 { 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;
// 優化:使用 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,
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,
COALESCE(app_stats.total_apps, 0) as total_apps,
COALESCE(review_stats.total_reviews, 0) as total_reviews
FROM users u
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}
`);
// 分頁資訊
const totalPages = Math.ceil(total / limit);
const hasNext = page < totalPages;
const hasPrev = page > 1;
// 格式化日期函數
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({
users: users.map(user => ({
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,
createdAt: formatDate(user.created_at),
updatedAt: formatDate(user.updated_at)
})),
pagination: { page, limit, total, totalPages, hasNext, hasPrev }
});
} catch (error) {
console.error('Error fetching users:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}

View File

@@ -1,48 +0,0 @@
import { NextRequest, NextResponse } from 'next/server';
import { verifyToken } from '@/lib/auth';
import { db } from '@/lib/database';
export async function GET(request: NextRequest) {
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 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: 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) {
console.error('Error fetching user stats:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}