diff --git a/README.md b/README.md new file mode 100644 index 0000000..597a9a1 --- /dev/null +++ b/README.md @@ -0,0 +1,232 @@ +# AI 展示平台 (AI Showcase Platform) + +一個現代化的 AI 應用展示和管理平台,支持競賽管理、評分系統和用戶互動功能。 + +## 🚀 功能特色 + +### 📱 應用管理 +- **應用展示**: 展示所有 AI 應用,支持分類和搜索 +- **分頁功能**: 每頁顯示5個應用,支持翻頁瀏覽 +- **統計數據**: 實時顯示總應用數、已發布數、已下架數 +- **應用詳情**: 查看應用的詳細信息、統計數據和用戶評價 + +### 🏆 競賽系統 +- **競賽管理**: 創建和管理不同類型的競賽(個人、團隊、混合) +- **評審系統**: 支持多評審評分,包含創新性、技術性、實用性等維度 +- **評分管理**: 實時追蹤評分進度和統計數據 +- **排名系統**: 自動計算和顯示競賽排名 + +### 👥 用戶管理 +- **用戶註冊**: 支持用戶註冊和登錄 +- **權限管理**: 區分普通用戶、開發者和管理員權限 +- **個人資料**: 用戶可以管理個人信息和偏好設置 + +### 📊 數據分析 +- **統計儀表板**: 實時顯示平台使用統計 +- **應用分析**: 查看應用的瀏覽量、點讚數、評分等數據 +- **競賽分析**: 競賽參與度和評分統計 + +## 🛠️ 技術棧 + +### 前端 +- **框架**: Next.js 14 (React 18) +- **樣式**: Tailwind CSS +- **UI 組件**: shadcn/ui +- **狀態管理**: React Context API +- **圖標**: Lucide React + +### 後端 +- **API**: Next.js API Routes +- **數據庫**: MySQL 8.0 +- **ORM**: 自定義數據庫服務層 +- **認證**: 自定義 JWT 認證系統 + +### 開發工具 +- **包管理**: pnpm +- **代碼格式化**: Prettier +- **類型檢查**: TypeScript +- **數據庫遷移**: 自定義遷移腳本 + +## 📦 安裝與設置 + +### 環境要求 +- Node.js 18.0.0 或更高版本 +- pnpm (推薦) 或 npm +- MySQL 8.0 或更高版本 + +### 1. 克隆項目 +```bash +git clone +cd ai-showcase-platform +``` + +### 2. 安裝依賴 +```bash +pnpm install +``` + +### 3. 環境配置 +複製環境變數範例文件: +```bash +cp env.example .env.local +``` + +編輯 `.env.local` 文件,填入您的數據庫配置: +```env +DB_HOST=your-database-host +DB_PORT=3306 +DB_USER=your-username +DB_PASSWORD=your-password +DB_NAME=your-database-name +``` + +### 4. 數據庫設置 +運行數據庫遷移: +```bash +# 創建數據庫結構 +node scripts/migrate-no-triggers.js + +# 填充示例數據 +node scripts/populate-sample-data.js +``` + +### 5. 啟動開發服務器 +```bash +pnpm dev +``` + +訪問 http://localhost:3000 查看應用。 + +## 📁 項目結構 + +``` +ai-showcase-platform/ +├── app/ # Next.js App Router +│ ├── admin/ # 管理員頁面 +│ ├── api/ # API 路由 +│ │ ├── admin/ # 管理員 API +│ │ ├── auth/ # 認證 API +│ │ ├── competitions/ # 競賽 API +│ │ └── user/ # 用戶 API +│ ├── competition/ # 競賽頁面 +│ └── page.tsx # 首頁 +├── components/ # React 組件 +│ ├── admin/ # 管理員組件 +│ ├── auth/ # 認證組件 +│ ├── competition/ # 競賽組件 +│ └── ui/ # UI 組件庫 +├── contexts/ # React Context +├── hooks/ # 自定義 Hooks +├── lib/ # 工具庫 +│ ├── services/ # 數據庫服務 +│ └── utils.ts # 工具函數 +├── scripts/ # 腳本文件 +│ ├── migrate-no-triggers.js # 數據庫遷移 +│ ├── populate-sample-data.js # 示例數據 +│ └── check-*.js # 檢查腳本 +├── types/ # TypeScript 類型定義 +└── public/ # 靜態資源 +``` + +## 🔧 開發指南 + +### 數據庫管理 +```bash +# 檢查數據庫連接 +node scripts/check-server.js + +# 檢查應用數據 +node scripts/check-apps-count.js + +# 清空數據庫 +node scripts/clear-database.js + +# 重新填充數據 +node scripts/populate-sample-data.js +``` + +### API 測試 +```bash +# 測試應用 API +node scripts/test-pagination-api.js + +# 測試分頁功能 +node scripts/test-pagination.js +``` + +### 代碼規範 +- 使用 TypeScript 進行類型檢查 +- 遵循 ESLint 規則 +- 使用 Prettier 格式化代碼 +- 組件使用函數式組件和 Hooks + +## 📊 數據庫設計 + +### 主要表結構 +- **users**: 用戶信息 +- **apps**: 應用信息 +- **competitions**: 競賽信息 +- **judges**: 評審信息 +- **app_judge_scores**: 應用評分 +- **competition_apps**: 競賽應用關聯 +- **competition_judges**: 競賽評審關聯 + +### 關係設計 +- 用戶與應用:一對多 +- 競賽與應用:多對多 +- 競賽與評審:多對多 +- 評審與評分:一對多 + +## 🚀 部署 + +### 生產環境設置 +1. 設置生產環境變數 +2. 配置數據庫連接 +3. 運行數據庫遷移 +4. 構建應用:`pnpm build` +5. 啟動應用:`pnpm start` + +### Docker 部署(可選) +```dockerfile +FROM node:18-alpine +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +RUN npm run build +EXPOSE 3000 +CMD ["npm", "start"] +``` + +## 🤝 貢獻指南 + +1. Fork 項目 +2. 創建功能分支:`git checkout -b feature/AmazingFeature` +3. 提交更改:`git commit -m 'Add some AmazingFeature'` +4. 推送到分支:`git push origin feature/AmazingFeature` +5. 開啟 Pull Request + +## 📝 更新日誌 + +### v1.0.0 (2024-09-19) +- ✨ 初始版本發布 +- 🎯 應用管理功能 +- 🏆 競賽系統 +- 👥 用戶管理 +- 📊 統計儀表板 +- 🔧 分頁功能優化 + +## 📄 許可證 + +此項目採用 MIT 許可證 - 查看 [LICENSE](LICENSE) 文件了解詳情。 + +## 📞 聯繫方式 + +如有問題或建議,請聯繫: +- 項目維護者:[您的姓名] +- 郵箱:[your-email@example.com] +- 項目地址:[repository-url] + +--- + +**注意**: 這是一個內部使用的 AI 展示平台,請確保在生產環境中進行適當的安全配置。 diff --git a/app/api/admin/apps/route.ts b/app/api/admin/apps/route.ts index 1481953..3c1f992 100644 --- a/app/api/admin/apps/route.ts +++ b/app/api/admin/apps/route.ts @@ -7,7 +7,7 @@ export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url) const page = parseInt(searchParams.get('page') || '1') - const limit = parseInt(searchParams.get('limit') || '10') + const limit = parseInt(searchParams.get('limit') || '5') const search = searchParams.get('search') || '' const category = searchParams.get('category') || 'all' const type = searchParams.get('type') || 'all' diff --git a/components/admin/app-management.tsx b/components/admin/app-management.tsx index 273e62b..18b2b2a 100644 --- a/components/admin/app-management.tsx +++ b/components/admin/app-management.tsx @@ -165,7 +165,7 @@ export function AppManagement() { }) const [pagination, setPagination] = useState({ page: 1, - limit: 10, + limit: 5, total: 0, totalPages: 0 }) @@ -608,7 +608,7 @@ export function AppManagement() {

總應用數

-

{apps.length}

+

{stats.totalApps}

@@ -620,7 +620,7 @@ export function AppManagement() {

已發布

-

{apps.filter((a) => a.status === "published").length}

+

{stats.activeApps}

@@ -632,7 +632,7 @@ export function AppManagement() {

已下架

-

{apps.filter((a) => a.status === "draft").length}

+

{stats.inactiveApps}

@@ -695,7 +695,7 @@ export function AppManagement() { {/* Apps Table */} - 應用列表 ({filteredApps.length}) + 應用列表 ({pagination.total}) 管理所有 AI 應用 @@ -842,6 +842,65 @@ export function AppManagement() { + {/* 分頁控制元件 */} + {pagination.totalPages > 1 && ( +
+
+ 第 {pagination.page} 頁,共 {pagination.totalPages} 頁 (共 {pagination.total} 個應用) +
+
+ + +
+ {Array.from({ length: Math.min(pagination.totalPages, 5) }, (_, i) => { + let page; + if (pagination.totalPages <= 5) { + page = i + 1; + } else if (pagination.page <= 3) { + page = i + 1; + } else if (pagination.page >= pagination.totalPages - 2) { + page = pagination.totalPages - 4 + i; + } else { + page = pagination.page - 2 + i; + } + + return ( + + ) + })} +
+ + +
+
+ )} + {/* Add App Dialog */} diff --git a/database-schema-simple.sql b/database-schema-simple.sql index db981f7..c3e86f4 100644 --- a/database-schema-simple.sql +++ b/database-schema-simple.sql @@ -475,7 +475,7 @@ BEGIN NEW.presentation_score + NEW.impact_score ) / 5.0; -END// +END; CREATE TRIGGER `calculate_app_total_score_update` BEFORE UPDATE ON `app_judge_scores` @@ -488,7 +488,7 @@ BEGIN NEW.presentation_score + NEW.impact_score ) / 5.0; -END// +END; CREATE TRIGGER `calculate_proposal_total_score` BEFORE INSERT ON `proposal_judge_scores` @@ -501,7 +501,7 @@ BEGIN NEW.impact_score + NEW.presentation_score ) / 5.0; -END// +END; CREATE TRIGGER `calculate_proposal_total_score_update` BEFORE UPDATE ON `proposal_judge_scores` @@ -514,9 +514,7 @@ BEGIN NEW.impact_score + NEW.presentation_score ) / 5.0; -END// - -DELIMITER ; +END; -- 視圖:用戶統計視圖 CREATE VIEW `user_statistics` AS diff --git a/lib/services/database-service.ts b/lib/services/database-service.ts index 379f366..e1446d1 100644 --- a/lib/services/database-service.ts +++ b/lib/services/database-service.ts @@ -1976,7 +1976,7 @@ export class AppService { limit?: number; } = {}): Promise<{ apps: any[]; total: number }> { try { - const { search = '', category = 'all', type = 'all', status = 'all', page = 1, limit = 10 } = filters; + const { search = '', category = 'all', type = 'all', status = 'all', page = 1, limit = 5 } = filters; // 構建查詢條件 let whereConditions: string[] = []; @@ -2846,11 +2846,12 @@ export class AppService { const appStatsSql = ` SELECT COUNT(*) as total_apps, + COUNT(CASE WHEN is_active = TRUE THEN 1 END) as active_apps, + COUNT(CASE WHEN is_active = FALSE THEN 1 END) as inactive_apps, COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_apps_this_month, COALESCE(SUM(views_count), 0) as total_views, COALESCE(SUM(likes_count), 0) as total_likes - FROM apps - WHERE is_active = TRUE + FROM apps `; const appStats = await this.queryOne(appStatsSql); @@ -2888,6 +2889,8 @@ export class AppService { totalUsers: userStats.totalUsers, activeUsers: userStats.activeUsers, totalApps: appStats?.total_apps || 0, + activeApps: appStats?.active_apps || 0, + inactiveApps: appStats?.inactive_apps || 0, totalCompetitions: competitionStats?.total_competitions || 0, totalReviews: reviewStats?.total_reviews || 0, totalViews: appStats?.total_views || 0, diff --git a/package.json b/package.json index 5d5ae04..60cdfdf 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "build": "next build", - "dev": "next dev", + "dev": "next dev -p 12016", "lint": "next lint", "start": "next start", "migrate": "node scripts/migrate.js", diff --git a/scripts/clear-database.js b/scripts/clear-database.js new file mode 100644 index 0000000..5963c11 --- /dev/null +++ b/scripts/clear-database.js @@ -0,0 +1,34 @@ +const mysql = require('mysql2/promise'); + +async function clearDatabase() { + const connection = await mysql.createConnection({ + host: 'mysql.theaken.com', + port: 33306, + user: 'AI_Platform', + password: 'Aa123456', + database: 'db_AI_Platform' + }); + + console.log('🗑️ 清空資料庫...'); + + // 清空所有表(按依賴順序) + const tables = [ + 'app_judge_scores', + 'competition_apps', + 'competition_judges', + 'apps', + 'judges', + 'competitions', + 'users' + ]; + + for (const table of tables) { + await connection.execute(`DELETE FROM ${table}`); + console.log(`✅ 清空了 ${table} 表`); + } + + await connection.end(); + console.log('🎉 資料庫清空完成!'); +} + +clearDatabase().catch(console.error); diff --git a/scripts/migrate-fixed.js b/scripts/migrate-fixed.js new file mode 100644 index 0000000..ef9eecc --- /dev/null +++ b/scripts/migrate-fixed.js @@ -0,0 +1,141 @@ +#!/usr/bin/env node + +// ===================================================== +// 資料庫遷移腳本 (修復版) +// ===================================================== + +const mysql = require('mysql2/promise'); +const fs = require('fs'); +const path = require('path'); + +// 資料庫配置 +const dbConfig = { + host: process.env.DB_HOST || 'mysql.theaken.com', + port: parseInt(process.env.DB_PORT || '33306'), + user: process.env.DB_USER || 'AI_Platform', + password: process.env.DB_PASSWORD || 'Aa123456', + database: process.env.DB_NAME || 'db_AI_Platform', + charset: 'utf8mb4', + timezone: '+08:00', +}; + +async function runMigration() { + let connection; + + try { + console.log('🚀 開始資料庫遷移...'); + + // 創建連接 + connection = await mysql.createConnection({ + ...dbConfig, + multipleStatements: true + }); + + console.log('✅ 資料庫連接成功'); + + // 讀取 SQL 文件 + const sqlFile = path.join(__dirname, '..', 'database-schema-simple.sql'); + const sqlContent = fs.readFileSync(sqlFile, 'utf8'); + + console.log('📖 讀取 SQL 文件成功'); + + // 處理 SQL 內容,正確分割語句 + console.log('⚡ 處理 SQL 語句...'); + + // 先處理觸發器,因為它們包含分號 + const triggerRegex = /CREATE TRIGGER[\s\S]*?END;/g; + const triggers = sqlContent.match(triggerRegex) || []; + + // 移除觸發器部分,處理其他語句 + let remainingSql = sqlContent.replace(triggerRegex, ''); + + // 分割其他語句 + const otherStatements = remainingSql + .split(';') + .map(stmt => stmt.trim()) + .filter(stmt => stmt.length > 0 && !stmt.startsWith('--')); + + // 合併所有語句 + const allStatements = [...otherStatements, ...triggers]; + + console.log(`📊 共找到 ${allStatements.length} 個語句`); + + // 執行語句 + for (let i = 0; i < allStatements.length; i++) { + const statement = allStatements[i]; + if (statement.trim()) { + try { + // 特殊處理 USE 語句和觸發器 + if (statement.toUpperCase().startsWith('USE') || + statement.toUpperCase().startsWith('CREATE TRIGGER')) { + await connection.query(statement + ';'); + } else { + await connection.execute(statement + ';'); + } + console.log(`✅ 執行語句 ${i + 1}/${allStatements.length}`); + } catch (error) { + console.error(`❌ 語句 ${i + 1} 執行失敗:`, error.message); + console.error(`語句內容: ${statement.substring(0, 100)}...`); + // 對於某些錯誤,我們可以繼續執行 + if (error.message.includes('already exists') || + error.message.includes('Duplicate entry') || + error.message.includes('Table') && error.message.includes('already exists')) { + console.log('⚠️ 跳過已存在的項目'); + continue; + } + throw error; + } + } + } + + console.log('✅ 資料庫結構創建成功!'); + + // 驗證表是否創建成功 + console.log('🔍 驗證表結構...'); + const [tables] = await connection.execute('SHOW TABLES'); + console.log(`📊 共創建了 ${tables.length} 個表:`); + + tables.forEach((table, index) => { + const tableName = Object.values(table)[0]; + console.log(` ${index + 1}. ${tableName}`); + }); + + // 檢查視圖 + console.log('🔍 驗證視圖...'); + const [views] = await connection.execute('SHOW FULL TABLES WHERE Table_type = "VIEW"'); + console.log(`📈 共創建了 ${views.length} 個視圖:`); + + views.forEach((view, index) => { + const viewName = Object.values(view)[0]; + console.log(` ${index + 1}. ${viewName}`); + }); + + // 檢查觸發器 + console.log('🔍 驗證觸發器...'); + const [triggerList] = await connection.execute('SHOW TRIGGERS'); + console.log(`⚙️ 共創建了 ${triggerList.length} 個觸發器:`); + + triggerList.forEach((trigger, index) => { + console.log(` ${index + 1}. ${trigger.Trigger}`); + }); + + console.log('🎉 資料庫遷移完成!'); + + } catch (error) { + console.error('❌ 遷移失敗:', error.message); + console.error('詳細錯誤:', error); + process.exit(1); + } finally { + if (connection) { + await connection.end(); + console.log('🔌 資料庫連接已關閉'); + } + } +} + +// 執行遷移 +if (require.main === module) { + runMigration().catch(console.error); +} + +module.exports = { runMigration }; diff --git a/scripts/migrate-no-triggers.js b/scripts/migrate-no-triggers.js new file mode 100644 index 0000000..6f48dcd --- /dev/null +++ b/scripts/migrate-no-triggers.js @@ -0,0 +1,124 @@ +#!/usr/bin/env node + +// ===================================================== +// 資料庫遷移腳本 (無觸發器版) +// ===================================================== + +const mysql = require('mysql2/promise'); +const fs = require('fs'); +const path = require('path'); + +// 資料庫配置 +const dbConfig = { + host: process.env.DB_HOST || 'mysql.theaken.com', + port: parseInt(process.env.DB_PORT || '33306'), + user: process.env.DB_USER || 'AI_Platform', + password: process.env.DB_PASSWORD || 'Aa123456', + database: process.env.DB_NAME || 'db_AI_Platform', + charset: 'utf8mb4', + timezone: '+08:00', +}; + +async function runMigration() { + let connection; + + try { + console.log('🚀 開始資料庫遷移...'); + + // 創建連接 + connection = await mysql.createConnection({ + ...dbConfig, + multipleStatements: true + }); + + console.log('✅ 資料庫連接成功'); + + // 讀取 SQL 文件 + const sqlFile = path.join(__dirname, '..', 'database-schema-simple.sql'); + const sqlContent = fs.readFileSync(sqlFile, 'utf8'); + + console.log('📖 讀取 SQL 文件成功'); + + // 移除觸發器部分 + console.log('⚡ 處理 SQL 語句...'); + const triggerRegex = /CREATE TRIGGER[\s\S]*?END;/g; + let sqlWithoutTriggers = sqlContent.replace(triggerRegex, ''); + + // 分割語句 + const statements = sqlWithoutTriggers + .split(';') + .map(stmt => stmt.trim()) + .filter(stmt => stmt.length > 0 && !stmt.startsWith('--')); + + console.log(`📊 共找到 ${statements.length} 個語句`); + + // 執行語句 + for (let i = 0; i < statements.length; i++) { + const statement = statements[i]; + if (statement.trim()) { + try { + // 特殊處理 USE 語句 + if (statement.toUpperCase().startsWith('USE')) { + await connection.query(statement + ';'); + } else { + await connection.execute(statement + ';'); + } + console.log(`✅ 執行語句 ${i + 1}/${statements.length}`); + } catch (error) { + console.error(`❌ 語句 ${i + 1} 執行失敗:`, error.message); + console.error(`語句內容: ${statement.substring(0, 100)}...`); + // 對於某些錯誤,我們可以繼續執行 + if (error.message.includes('already exists') || + error.message.includes('Duplicate entry') || + error.message.includes('Table') && error.message.includes('already exists')) { + console.log('⚠️ 跳過已存在的項目'); + continue; + } + throw error; + } + } + } + + console.log('✅ 資料庫結構創建成功!'); + + // 驗證表是否創建成功 + console.log('🔍 驗證表結構...'); + const [tables] = await connection.execute('SHOW TABLES'); + console.log(`📊 共創建了 ${tables.length} 個表:`); + + tables.forEach((table, index) => { + const tableName = Object.values(table)[0]; + console.log(` ${index + 1}. ${tableName}`); + }); + + // 檢查視圖 + console.log('🔍 驗證視圖...'); + const [views] = await connection.execute('SHOW FULL TABLES WHERE Table_type = "VIEW"'); + console.log(`📈 共創建了 ${views.length} 個視圖:`); + + views.forEach((view, index) => { + const viewName = Object.values(view)[0]; + console.log(` ${index + 1}. ${viewName}`); + }); + + console.log('🎉 資料庫遷移完成!'); + console.log('⚠️ 注意:觸發器由於權限限制未創建,但基本表結構已就緒'); + + } catch (error) { + console.error('❌ 遷移失敗:', error.message); + console.error('詳細錯誤:', error); + process.exit(1); + } finally { + if (connection) { + await connection.end(); + console.log('🔌 資料庫連接已關閉'); + } + } +} + +// 執行遷移 +if (require.main === module) { + runMigration().catch(console.error); +} + +module.exports = { runMigration }; diff --git a/scripts/populate-sample-data.js b/scripts/populate-sample-data.js new file mode 100644 index 0000000..ec41f06 --- /dev/null +++ b/scripts/populate-sample-data.js @@ -0,0 +1,319 @@ +#!/usr/bin/env node + +// ===================================================== +// 填充示例數據腳本 +// ===================================================== + +const mysql = require('mysql2/promise'); +const { v4: uuidv4 } = require('uuid'); + +// 資料庫配置 +const dbConfig = { + host: process.env.DB_HOST || 'mysql.theaken.com', + port: parseInt(process.env.DB_PORT || '33306'), + user: process.env.DB_USER || 'AI_Platform', + password: process.env.DB_PASSWORD || 'Aa123456', + database: process.env.DB_NAME || 'db_AI_Platform', + charset: 'utf8mb4', + timezone: '+08:00', +}; + +async function populateSampleData() { + let connection; + + try { + console.log('🚀 開始填充示例數據...'); + + // 創建連接 + connection = await mysql.createConnection(dbConfig); + console.log('✅ 資料庫連接成功'); + + // 1. 創建示例用戶 + console.log('👥 創建示例用戶...'); + const users = [ + { + id: uuidv4(), + name: '張小明', + email: 'zhang.xiaoming@company.com', + password_hash: '$2b$10$example.hash.here', // 示例哈希 + department: 'HQBU', + role: 'developer', + join_date: '2024-01-15', + total_likes: 25, + total_views: 150, + is_active: true + }, + { + id: uuidv4(), + name: '李美華', + email: 'li.meihua@company.com', + password_hash: '$2b$10$example.hash.here', + department: 'ITBU', + role: 'developer', + join_date: '2024-02-01', + total_likes: 18, + total_views: 120, + is_active: true + }, + { + id: uuidv4(), + name: '王大偉', + email: 'wang.dawei@company.com', + password_hash: '$2b$10$example.hash.here', + department: 'MBU1', + role: 'developer', + join_date: '2024-01-20', + total_likes: 32, + total_views: 200, + is_active: true + }, + { + id: uuidv4(), + name: '陳小芳', + email: 'chen.xiaofang@company.com', + password_hash: '$2b$10$example.hash.here', + department: 'SBU', + role: 'developer', + join_date: '2024-02-10', + total_likes: 15, + total_views: 90, + is_active: true + }, + { + id: uuidv4(), + name: '劉志強', + email: 'liu.zhiqiang@company.com', + password_hash: '$2b$10$example.hash.here', + department: 'HQBU', + role: 'admin', + join_date: '2023-12-01', + total_likes: 5, + total_views: 50, + is_active: true + } + ]; + + for (const user of users) { + await connection.execute( + `INSERT INTO users (id, name, email, password_hash, department, role, join_date, total_likes, total_views, status, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`, + [user.id, user.name, user.email, user.password_hash, user.department, user.role, user.join_date, user.total_likes, user.total_views, 'active'] + ); + } + console.log(`✅ 創建了 ${users.length} 個用戶`); + + // 2. 創建示例評審 + console.log('👨‍⚖️ 創建示例評審...'); + const judges = [ + { + id: uuidv4(), + name: '王教授', + title: '技術總監', + department: 'ITBU', + expertise: JSON.stringify(['AI', '機器學習', '深度學習']), + is_active: true + }, + { + id: uuidv4(), + name: '李博士', + title: '產品經理', + department: 'HQBU', + expertise: JSON.stringify(['產品設計', '用戶體驗', '商業分析']), + is_active: true + }, + { + id: uuidv4(), + name: '陳工程師', + title: '資深工程師', + department: 'MBU1', + expertise: JSON.stringify(['軟體開發', '系統架構', '資料庫']), + is_active: true + } + ]; + + for (const judge of judges) { + await connection.execute( + `INSERT INTO judges (id, name, title, department, expertise, is_active, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())`, + [judge.id, judge.name, judge.title, judge.department, judge.expertise, judge.is_active] + ); + } + console.log(`✅ 創建了 ${judges.length} 個評審`); + + // 3. 創建示例競賽 + console.log('🏆 創建示例競賽...'); + const competitions = [ + { + id: uuidv4(), + name: '2024年AI創新競賽', + description: '展示最新的AI技術創新成果', + type: 'individual', + year: 2024, + month: 3, + start_date: '2024-03-01', + end_date: '2024-03-31', + status: 'active', + is_current: true, + is_active: true, + evaluation_focus: JSON.stringify(['創新性', '技術性', '實用性']), + max_team_size: 5 + }, + { + id: uuidv4(), + name: '2024年團隊協作競賽', + description: '團隊協作開發的AI應用', + type: 'team', + year: 2024, + month: 4, + start_date: '2024-04-01', + end_date: '2024-04-30', + status: 'upcoming', + is_current: false, + is_active: true, + evaluation_focus: JSON.stringify(['團隊協作', '技術實現', '創新應用']), + max_team_size: 8 + } + ]; + + for (const competition of competitions) { + await connection.execute( + `INSERT INTO competitions (id, name, description, type, year, month, start_date, end_date, status, is_current, is_active, evaluation_focus, max_team_size, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`, + [competition.id, competition.name, competition.description, competition.type, competition.year, competition.month, + competition.start_date, competition.end_date, competition.status, competition.is_current, competition.is_active, + competition.evaluation_focus, competition.max_team_size] + ); + } + console.log(`✅ 創建了 ${competitions.length} 個競賽`); + + // 4. 創建示例應用 + console.log('📱 創建示例應用...'); + const apps = [ + { + id: uuidv4(), + name: '智能對話助手', + description: '基於大語言模型的智能對話系統', + creator_id: users[0].id, + team_id: null, + category: '文字處理', + technology_stack: JSON.stringify(['Python', 'OpenAI API', 'React']), + github_url: 'https://github.com/example/chatbot', + demo_url: 'https://demo.example.com/chatbot', + status: 'published', + is_active: true, + total_likes: 25, + total_views: 150 + }, + { + id: uuidv4(), + name: '圖像生成工具', + description: 'AI驅動的創意圖像生成平台', + creator_id: users[1].id, + team_id: null, + category: '圖像生成', + technology_stack: JSON.stringify(['Python', 'Stable Diffusion', 'FastAPI']), + github_url: 'https://github.com/example/image-gen', + demo_url: 'https://demo.example.com/image-gen', + status: 'published', + is_active: true, + total_likes: 18, + total_views: 120 + }, + { + id: uuidv4(), + name: '語音識別系統', + description: '高精度多語言語音識別服務', + creator_id: users[2].id, + team_id: null, + category: '語音辨識', + technology_stack: JSON.stringify(['Python', 'Whisper', 'Docker']), + github_url: 'https://github.com/example/speech-recognition', + demo_url: 'https://demo.example.com/speech', + status: 'published', + is_active: true, + total_likes: 32, + total_views: 200 + } + ]; + + for (const app of apps) { + await connection.execute( + `INSERT INTO apps (id, name, description, creator_id, team_id, category, type, app_url, likes_count, views_count, is_active, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())`, + [app.id, app.name, app.description, app.creator_id, app.team_id, app.category, 'web', app.demo_url, app.total_likes, app.total_views, 1] + ); + } + console.log(`✅ 創建了 ${apps.length} 個應用`); + + // 5. 關聯競賽和應用 + console.log('🔗 關聯競賽和應用...'); + const currentCompetition = competitions[0]; // 當前競賽 + for (const app of apps) { + await connection.execute( + `INSERT INTO competition_apps (id, competition_id, app_id, submitted_at) VALUES (?, ?, ?, NOW())`, + [uuidv4(), currentCompetition.id, app.id] + ); + } + console.log(`✅ 關聯了 ${apps.length} 個應用到當前競賽`); + + // 6. 關聯競賽和評審 + console.log('🔗 關聯競賽和評審...'); + for (const judge of judges) { + await connection.execute( + `INSERT INTO competition_judges (id, competition_id, judge_id, assigned_at) VALUES (?, ?, ?, NOW())`, + [uuidv4(), currentCompetition.id, judge.id] + ); + } + console.log(`✅ 關聯了 ${judges.length} 個評審到當前競賽`); + + // 7. 創建示例評分 + console.log('📊 創建示例評分...'); + for (const app of apps) { + for (const judge of judges) { + const scores = { + innovation_score: Math.floor(Math.random() * 5) + 1, + technical_score: Math.floor(Math.random() * 5) + 1, + usability_score: Math.floor(Math.random() * 5) + 1, + presentation_score: Math.floor(Math.random() * 5) + 1, + impact_score: Math.floor(Math.random() * 5) + 1 + }; + + const totalScore = (scores.innovation_score + scores.technical_score + scores.usability_score + + scores.presentation_score + scores.impact_score) / 5; + + await connection.execute( + `INSERT INTO app_judge_scores (id, judge_id, app_id, innovation_score, technical_score, usability_score, presentation_score, impact_score, total_score, comments, submitted_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`, + [uuidv4(), judge.id, app.id, scores.innovation_score, scores.technical_score, scores.usability_score, + scores.presentation_score, scores.impact_score, totalScore, '示例評分'] + ); + } + } + console.log(`✅ 創建了 ${apps.length * judges.length} 個評分記錄`); + + console.log('🎉 示例數據填充完成!'); + console.log('\n📊 數據摘要:'); + console.log(`- 用戶: ${users.length} 個`); + console.log(`- 評審: ${judges.length} 個`); + console.log(`- 競賽: ${competitions.length} 個 (其中 1 個為當前競賽)`); + console.log(`- 應用: ${apps.length} 個`); + console.log(`- 評分記錄: ${apps.length * judges.length} 個`); + + } catch (error) { + console.error('❌ 填充失敗:', error.message); + console.error('詳細錯誤:', error); + process.exit(1); + } finally { + if (connection) { + await connection.end(); + console.log('🔌 資料庫連接已關閉'); + } + } +} + +// 執行填充 +if (require.main === module) { + populateSampleData().catch(console.error); +} + +module.exports = { populateSampleData };