檔案上傳新增至資料庫

This commit is contained in:
2025-09-23 18:29:17 +08:00
parent b6e4f30712
commit ec7d101e96
5 changed files with 247 additions and 9 deletions

140
app/api/upload/route.ts Normal file
View File

@@ -0,0 +1,140 @@
import { NextRequest, NextResponse } from 'next/server';
import { writeFile, mkdir } from 'fs/promises';
import { ProjectService, ProjectFileService, CriteriaItemService } from '@/lib/services/database';
import {
getUploadAbsolutePath,
getUploadRelativePath,
getUploadDirPath,
generateUniqueFileName,
isValidFileType,
isValidFileSize
} from '@/lib/utils/file-path';
export async function POST(request: NextRequest) {
try {
const formData = await request.formData();
const projectTitle = formData.get('projectTitle') as string;
const projectDescription = formData.get('projectDescription') as string;
const file = formData.get('file') as File;
console.log('🚀 開始處理檔案上傳...');
console.log('📝 專案標題:', projectTitle);
console.log('📋 專案描述:', projectDescription);
console.log('📁 上傳文件:', file ? file.name : '無');
// 驗證必填欄位
if (!projectTitle?.trim()) {
return NextResponse.json(
{ success: false, error: '請填寫專案標題' },
{ status: 400 }
);
}
if (!file) {
return NextResponse.json(
{ success: false, error: '請上傳文件' },
{ status: 400 }
);
}
// 驗證檔案類型
if (!isValidFileType(file.type)) {
return NextResponse.json(
{ success: false, error: '不支援的檔案類型' },
{ status: 400 }
);
}
// 驗證檔案大小100MB
if (!isValidFileSize(file.size, 100)) {
return NextResponse.json(
{ success: false, error: '檔案大小超過 100MB 限制' },
{ status: 400 }
);
}
// 獲取預設評分標準模板
const templates = await CriteriaItemService.getAllTemplates();
if (!templates || templates.length === 0) {
return NextResponse.json(
{ success: false, error: '未找到評分標準,請先設定評分標準' },
{ status: 400 }
);
}
const templateId = templates[0].id;
console.log('📊 使用評分標準模板 ID:', templateId);
// 創建專案記錄
const projectData = {
user_id: 1, // 暫時使用固定用戶 ID
template_id: templateId,
title: projectTitle,
description: projectDescription || null,
status: 'uploading' as const, // 初始狀態為上傳中
analysis_started_at: null,
analysis_completed_at: null
};
console.log('💾 創建專案記錄...');
const projectResult = await ProjectService.create(projectData);
const projectId = (projectResult as any).insertId;
console.log('✅ 專案記錄創建成功ID:', projectId);
// 準備檔案儲存
const uploadDir = getUploadDirPath(projectId);
await mkdir(uploadDir, { recursive: true });
// 生成唯一檔案名稱
const uniqueFileName = generateUniqueFileName(file.name);
const filePath = getUploadAbsolutePath(projectId, uniqueFileName);
const relativeFilePath = getUploadRelativePath(projectId, uniqueFileName);
// 儲存檔案
console.log('💾 儲存檔案到:', filePath);
console.log('📁 相對路徑:', relativeFilePath);
const bytes = await file.arrayBuffer();
await writeFile(filePath, Buffer.from(bytes));
// 創建檔案記錄
const fileExtension = file.name.split('.').pop();
const fileData = {
project_id: projectId,
original_name: file.name,
file_name: uniqueFileName,
file_path: relativeFilePath, // 使用相對路徑
file_size: file.size,
file_type: fileExtension || '',
mime_type: file.type,
upload_status: 'completed' as const,
upload_progress: 100
};
console.log('💾 創建檔案記錄...');
await ProjectFileService.create(fileData);
console.log('✅ 檔案記錄創建成功');
// 更新專案狀態為 completed檔案上傳完成
console.log('🔄 更新專案狀態為 completed...');
await ProjectService.update(projectId, { status: 'completed' });
console.log('✅ 專案狀態更新完成');
return NextResponse.json({
success: true,
data: {
projectId,
fileName: file.name,
fileSize: file.size,
fileType: file.type
},
message: '檔案上傳成功'
});
} catch (error) {
console.error('❌ 檔案上傳失敗:', error);
return NextResponse.json(
{ success: false, error: '檔案上傳失敗,請稍後再試' },
{ status: 500 }
);
}
}

View File

@@ -135,25 +135,56 @@ export default function UploadPage() {
setIsAnalyzing(true)
try {
console.log('🚀 開始 AI 評審流程...')
console.log('🚀 開始上傳和評審流程...')
console.log('📝 專案標題:', projectTitle)
console.log('📋 專案描述:', projectDescription)
console.log('📁 上傳文件數量:', files.length)
console.log('🌐 網站連結:', websiteUrl)
// 準備表單數據
let projectId = null;
// 如果有文件,先上傳到資料庫
if (files.length > 0) {
console.log('📤 上傳文件到資料庫...')
const firstFile = files[0]
if (firstFile.file) {
const uploadFormData = new FormData()
uploadFormData.append('projectTitle', projectTitle)
uploadFormData.append('projectDescription', projectDescription)
uploadFormData.append('file', firstFile.file)
const uploadResponse = await fetch('/api/upload', {
method: 'POST',
body: uploadFormData,
})
const uploadResult = await uploadResponse.json()
if (uploadResult.success) {
projectId = uploadResult.data.projectId
console.log('✅ 文件上傳成功,專案 ID:', projectId)
toast({
title: "文件上傳成功",
description: `專案已創建ID: ${projectId}`,
})
} else {
throw new Error(uploadResult.error || '文件上傳失敗')
}
} else {
throw new Error('文件對象遺失,請重新上傳')
}
}
// 準備 AI 評審數據
const formData = new FormData()
formData.append('projectTitle', projectTitle)
formData.append('projectDescription', projectDescription)
if (files.length > 0) {
// 只處理第一個文件(可以後續擴展支援多文件)
const firstFile = files[0]
if (firstFile.file) {
formData.append('file', firstFile.file)
console.log('📄 處理文件:', firstFile.name, '大小:', firstFile.size)
} else {
throw new Error('文件對象遺失,請重新上傳')
}
}
@@ -162,8 +193,8 @@ export default function UploadPage() {
console.log('🌐 處理網站連結:', websiteUrl)
}
// 發送評審請求
console.log('📤 發送評審請求到 API...')
// 發送 AI 評審請求
console.log('🤖 開始 AI 評審...')
const response = await fetch('/api/evaluate', {
method: 'POST',
body: formData,
@@ -195,9 +226,9 @@ export default function UploadPage() {
throw new Error(result.error || '評審失敗')
}
} catch (error) {
console.error('❌ AI 評審失敗:', error)
console.error('❌ 上傳或評審失敗:', error)
toast({
title: "評審失敗",
title: "操作失敗",
description: error instanceof Error ? error.message : "請稍後再試",
variant: "destructive",
})