刪除不必要檔案
This commit is contained in:
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
@@ -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 }
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user