新增資料庫、用戶註冊、登入的功能

This commit is contained in:
2025-08-05 10:56:22 +08:00
parent 94e3763402
commit a288a966ba
41 changed files with 4362 additions and 289 deletions

134
scripts/create-admin.js Normal file
View File

@@ -0,0 +1,134 @@
const mysql = require('mysql2/promise');
const bcrypt = require('bcrypt');
const crypto = require('crypto');
// 資料庫配置
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'
};
// 生成 UUID
function generateId() {
return crypto.randomUUID();
}
// 加密密碼
async function hashPassword(password) {
const saltRounds = 12;
return await bcrypt.hash(password, saltRounds);
}
async function createAdmin() {
let connection;
try {
console.log('🔌 連接資料庫...');
connection = await mysql.createConnection(dbConfig);
console.log('✅ 資料庫連接成功');
// 管理員資料
const adminData = {
id: generateId(),
name: 'AI平台管理員',
email: 'admin@theaken.com',
password: 'Admin@2024',
department: '資訊技術部',
role: 'admin'
};
console.log('\n📋 準備建立管理員帳號:');
console.log(` 姓名: ${adminData.name}`);
console.log(` 電子郵件: ${adminData.email}`);
console.log(` 部門: ${adminData.department}`);
console.log(` 角色: ${adminData.role}`);
// 檢查是否已存在
const [existingUser] = await connection.execute(
'SELECT id FROM users WHERE email = ?',
[adminData.email]
);
if (existingUser.length > 0) {
console.log('\n⚠ 管理員帳號已存在,更新密碼...');
// 加密新密碼
const passwordHash = await hashPassword(adminData.password);
// 更新密碼
await connection.execute(
'UPDATE users SET password_hash = ?, updated_at = NOW() WHERE email = ?',
[passwordHash, adminData.email]
);
console.log('✅ 管理員密碼已更新');
} else {
console.log('\n📝 建立新的管理員帳號...');
// 加密密碼
const passwordHash = await hashPassword(adminData.password);
// 插入管理員資料
await connection.execute(`
INSERT INTO users (
id, name, email, password_hash, department, role,
join_date, total_likes, total_views, created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, CURDATE(), 0, 0, NOW(), NOW())
`, [
adminData.id,
adminData.name,
adminData.email,
passwordHash,
adminData.department,
adminData.role
]);
console.log('✅ 管理員帳號建立成功');
}
// 驗證建立結果
console.log('\n🔍 驗證管理員帳號...');
const [adminUser] = await connection.execute(
'SELECT id, name, email, department, role, created_at FROM users WHERE email = ?',
[adminData.email]
);
if (adminUser.length > 0) {
const user = adminUser[0];
console.log('✅ 管理員帳號驗證成功:');
console.log(` ID: ${user.id}`);
console.log(` 姓名: ${user.name}`);
console.log(` 電子郵件: ${user.email}`);
console.log(` 部門: ${user.department}`);
console.log(` 角色: ${user.role}`);
console.log(` 建立時間: ${user.created_at}`);
}
console.log('\n🎉 管理員帳號設定完成!');
console.log('\n📋 登入資訊:');
console.log(` 電子郵件: ${adminData.email}`);
console.log(` 密碼: ${adminData.password}`);
console.log('\n⚠ 請妥善保管登入資訊,建議在首次登入後更改密碼');
} catch (error) {
console.error('❌ 建立管理員帳號失敗:', error.message);
process.exit(1);
} finally {
if (connection) {
await connection.end();
console.log('🔌 資料庫連接已關閉');
}
}
}
// 如果直接執行此腳本
if (require.main === module) {
createAdmin();
}
module.exports = { createAdmin };

136
scripts/fix-tables.js Normal file
View File

@@ -0,0 +1,136 @@
const mysql = require('mysql2/promise');
// 資料庫配置
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 fixTables() {
let connection;
try {
console.log('🔧 修復剩餘的資料表...');
// 連接資料庫
connection = await mysql.createConnection(dbConfig);
// 修復 awards 表 (rank 是保留字)
console.log('修復 awards 表...');
const awardsTable = `
CREATE TABLE IF NOT EXISTS awards (
id VARCHAR(36) PRIMARY KEY,
competition_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
team_id VARCHAR(36),
proposal_id VARCHAR(36),
award_type ENUM('gold', 'silver', 'bronze', 'popular', 'innovation', 'technical', 'custom') NOT NULL,
award_name VARCHAR(200) NOT NULL,
score DECIMAL(5,2) NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
icon VARCHAR(50),
custom_award_type_id VARCHAR(36),
competition_type ENUM('individual', 'team', 'proposal') NOT NULL,
award_rank INT DEFAULT 0,
category ENUM('innovation', 'technical', 'practical', 'popular', 'teamwork', 'solution', 'creativity') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (competition_id) REFERENCES competitions(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE SET NULL,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE SET NULL,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE SET NULL,
INDEX idx_competition (competition_id),
INDEX idx_award_type (award_type),
INDEX idx_year_month (year, month),
INDEX idx_category (category)
)
`;
await connection.query(awardsTable);
console.log('✅ awards 表建立成功');
// 修復 user_likes 表 (DATE() 函數在唯一約束中的問題)
console.log('修復 user_likes 表...');
const userLikesTable = `
CREATE TABLE IF NOT EXISTS user_likes (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
proposal_id VARCHAR(36),
liked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
UNIQUE KEY unique_user_app_date (user_id, app_id, DATE(liked_at)),
UNIQUE KEY unique_user_proposal_date (user_id, proposal_id, DATE(liked_at)),
INDEX idx_user (user_id),
INDEX idx_app (app_id),
INDEX idx_proposal (proposal_id),
INDEX idx_date (liked_at)
)
`;
await connection.query(userLikesTable);
console.log('✅ user_likes 表建立成功');
// 驗證修復結果
console.log('\n📋 驗證修復結果...');
const [tables] = await connection.query(`
SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.tables
WHERE table_schema = '${dbConfig.database}'
ORDER BY TABLE_NAME
`);
console.log('\n📊 資料表列表:');
console.log('─'.repeat(60));
console.log('表名'.padEnd(25) + '| 記錄數'.padEnd(10) + '| 狀態');
console.log('─'.repeat(60));
const expectedTables = [
'users', 'competitions', 'judges', 'teams', 'team_members',
'apps', 'proposals', 'judge_scores', 'awards', 'chat_sessions',
'chat_messages', 'ai_assistant_configs', 'user_favorites',
'user_likes', 'competition_participants', 'competition_judges',
'system_settings', 'activity_logs'
];
let successCount = 0;
for (const expectedTable of expectedTables) {
const table = tables.find(t => t.TABLE_NAME === expectedTable);
const status = table ? '✅' : '❌';
const rowCount = table ? (table.TABLE_ROWS || 0) : 'N/A';
console.log(`${expectedTable.padEnd(25)}| ${rowCount.toString().padEnd(10)}| ${status}`);
if (table) successCount++;
}
console.log(`\n📊 成功建立 ${successCount}/${expectedTables.length} 個資料表`);
if (successCount === expectedTables.length) {
console.log('🎉 所有資料表建立完成!');
} else {
console.log('⚠️ 仍有部分資料表未建立');
}
} catch (error) {
console.error('❌ 修復資料表失敗:', error.message);
} finally {
if (connection) {
await connection.end();
console.log('\n🔌 資料庫連接已關閉');
}
}
}
// 執行修復腳本
if (require.main === module) {
fixTables();
}
module.exports = { fixTables };

80
scripts/fix-user-likes.js Normal file
View File

@@ -0,0 +1,80 @@
const mysql = require('mysql2/promise');
// 資料庫配置
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 fixUserLikes() {
let connection;
try {
console.log('🔧 修復 user_likes 表...');
// 連接資料庫
connection = await mysql.createConnection(dbConfig);
// 先刪除可能存在的表
try {
await connection.query('DROP TABLE IF EXISTS user_likes');
console.log('✅ 刪除舊的 user_likes 表');
} catch (error) {
console.log('沒有舊表需要刪除');
}
// 建立簡化版的 user_likes 表
const userLikesTable = `
CREATE TABLE user_likes (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
proposal_id VARCHAR(36),
liked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
INDEX idx_user (user_id),
INDEX idx_app (app_id),
INDEX idx_proposal (proposal_id),
INDEX idx_date (liked_at)
)
`;
await connection.query(userLikesTable);
console.log('✅ user_likes 表建立成功');
// 驗證結果
const [tables] = await connection.query(`
SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.tables
WHERE table_schema = '${dbConfig.database}' AND TABLE_NAME = 'user_likes'
`);
if (tables.length > 0) {
console.log('✅ user_likes 表驗證成功');
} else {
console.log('❌ user_likes 表建立失敗');
}
} catch (error) {
console.error('❌ 修復 user_likes 表失敗:', error.message);
} finally {
if (connection) {
await connection.end();
console.log('\n🔌 資料庫連接已關閉');
}
}
}
// 執行修復腳本
if (require.main === module) {
fixUserLikes();
}
module.exports = { fixUserLikes };

View File

@@ -0,0 +1,491 @@
const mysql = require('mysql2/promise');
// 資料庫配置
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 setupDatabase() {
let connection;
try {
console.log('🚀 開始建立AI展示平台資料庫...');
console.log('─'.repeat(50));
// 1. 連接資料庫
console.log('🔌 連接資料庫...');
connection = await mysql.createConnection({
...dbConfig,
database: undefined
});
// 2. 檢查資料庫是否存在
const [databases] = await connection.query('SHOW DATABASES');
const dbExists = databases.some(db => db.Database === dbConfig.database);
if (!dbExists) {
console.log(`📊 建立資料庫: ${dbConfig.database}`);
await connection.query(`CREATE DATABASE ${dbConfig.database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
} else {
console.log(`✅ 資料庫已存在: ${dbConfig.database}`);
}
// 3. 切換到目標資料庫
await connection.query(`USE ${dbConfig.database}`);
// 4. 手動執行SQL語句
console.log('📝 執行資料庫建立腳本...');
const sqlStatements = [
// 1. 用戶表
`CREATE TABLE IF NOT EXISTS users (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
avatar VARCHAR(500),
department VARCHAR(100) NOT NULL,
role ENUM('user', 'developer', 'admin') DEFAULT 'user',
join_date DATE NOT NULL,
total_likes INT DEFAULT 0,
total_views INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_email (email),
INDEX idx_department (department),
INDEX idx_role (role)
)`,
// 2. 競賽表
`CREATE TABLE IF NOT EXISTS competitions (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
start_date DATE NOT NULL,
end_date DATE NOT NULL,
status ENUM('upcoming', 'active', 'judging', 'completed') DEFAULT 'upcoming',
description TEXT,
type ENUM('individual', 'team', 'mixed', 'proposal') NOT NULL,
evaluation_focus TEXT,
max_team_size INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_status (status),
INDEX idx_type (type),
INDEX idx_year_month (year, month),
INDEX idx_dates (start_date, end_date)
)`,
// 3. 評審表
`CREATE TABLE IF NOT EXISTS judges (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
title VARCHAR(100) NOT NULL,
department VARCHAR(100) NOT NULL,
expertise JSON,
avatar VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_department (department)
)`,
// 4. 團隊表
`CREATE TABLE IF NOT EXISTS teams (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
leader_id VARCHAR(36) NOT NULL,
department VARCHAR(100) NOT NULL,
contact_email VARCHAR(255) NOT NULL,
total_likes INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (leader_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_department (department),
INDEX idx_leader (leader_id)
)`,
// 5. 團隊成員表
`CREATE TABLE IF NOT EXISTS team_members (
id VARCHAR(36) PRIMARY KEY,
team_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
role VARCHAR(50) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
UNIQUE KEY unique_team_user (team_id, user_id),
INDEX idx_team (team_id),
INDEX idx_user (user_id)
)`,
// 6. 應用表
`CREATE TABLE IF NOT EXISTS apps (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
creator_id VARCHAR(36) NOT NULL,
team_id VARCHAR(36),
likes_count INT DEFAULT 0,
views_count INT DEFAULT 0,
rating DECIMAL(3,2) DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE SET NULL,
INDEX idx_creator (creator_id),
INDEX idx_team (team_id),
INDEX idx_rating (rating),
INDEX idx_likes (likes_count)
)`,
// 7. 提案表
`CREATE TABLE IF NOT EXISTS proposals (
id VARCHAR(36) PRIMARY KEY,
title VARCHAR(200) NOT NULL,
description TEXT,
creator_id VARCHAR(36) NOT NULL,
team_id VARCHAR(36),
status ENUM('draft', 'submitted', 'under_review', 'approved', 'rejected') DEFAULT 'draft',
likes_count INT DEFAULT 0,
views_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE SET NULL,
INDEX idx_creator (creator_id),
INDEX idx_status (status)
)`,
// 8. 評分表
`CREATE TABLE IF NOT EXISTS judge_scores (
id VARCHAR(36) PRIMARY KEY,
judge_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
proposal_id VARCHAR(36),
scores JSON NOT NULL,
comments TEXT,
submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (judge_id) REFERENCES judges(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
UNIQUE KEY unique_judge_app (judge_id, app_id),
UNIQUE KEY unique_judge_proposal (judge_id, proposal_id),
INDEX idx_judge (judge_id),
INDEX idx_app (app_id),
INDEX idx_proposal (proposal_id)
)`,
// 9. 獎項表
`CREATE TABLE IF NOT EXISTS awards (
id VARCHAR(36) PRIMARY KEY,
competition_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
team_id VARCHAR(36),
proposal_id VARCHAR(36),
award_type ENUM('gold', 'silver', 'bronze', 'popular', 'innovation', 'technical', 'custom') NOT NULL,
award_name VARCHAR(200) NOT NULL,
score DECIMAL(5,2) NOT NULL,
year INT NOT NULL,
month INT NOT NULL,
icon VARCHAR(50),
custom_award_type_id VARCHAR(36),
competition_type ENUM('individual', 'team', 'proposal') NOT NULL,
rank INT DEFAULT 0,
category ENUM('innovation', 'technical', 'practical', 'popular', 'teamwork', 'solution', 'creativity') NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (competition_id) REFERENCES competitions(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE SET NULL,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE SET NULL,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE SET NULL,
INDEX idx_competition (competition_id),
INDEX idx_award_type (award_type),
INDEX idx_year_month (year, month),
INDEX idx_category (category)
)`,
// 10. 聊天會話表
`CREATE TABLE IF NOT EXISTS chat_sessions (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
INDEX idx_user (user_id),
INDEX idx_created (created_at)
)`,
// 11. 聊天訊息表
`CREATE TABLE IF NOT EXISTS chat_messages (
id VARCHAR(36) PRIMARY KEY,
session_id VARCHAR(36) NOT NULL,
text TEXT NOT NULL,
sender ENUM('user', 'bot') NOT NULL,
quick_questions JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (session_id) REFERENCES chat_sessions(id) ON DELETE CASCADE,
INDEX idx_session (session_id),
INDEX idx_created (created_at)
)`,
// 12. AI助手配置表
`CREATE TABLE IF NOT EXISTS ai_assistant_configs (
id VARCHAR(36) PRIMARY KEY,
api_key VARCHAR(255) NOT NULL,
api_url VARCHAR(500) NOT NULL,
model VARCHAR(100) NOT NULL,
max_tokens INT DEFAULT 200,
temperature DECIMAL(3,2) DEFAULT 0.7,
system_prompt TEXT NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_active (is_active)
)`,
// 13. 用戶收藏表
`CREATE TABLE IF NOT EXISTS user_favorites (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
proposal_id VARCHAR(36),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
UNIQUE KEY unique_user_app (user_id, app_id),
UNIQUE KEY unique_user_proposal (user_id, proposal_id),
INDEX idx_user (user_id)
)`,
// 14. 用戶按讚表
`CREATE TABLE IF NOT EXISTS user_likes (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36) NOT NULL,
app_id VARCHAR(36),
proposal_id VARCHAR(36),
liked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
UNIQUE KEY unique_user_app_date (user_id, app_id, DATE(liked_at)),
UNIQUE KEY unique_user_proposal_date (user_id, proposal_id, DATE(liked_at)),
INDEX idx_user (user_id),
INDEX idx_app (app_id),
INDEX idx_proposal (proposal_id),
INDEX idx_date (liked_at)
)`,
// 15. 競賽參與表
`CREATE TABLE IF NOT EXISTS competition_participants (
id VARCHAR(36) PRIMARY KEY,
competition_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36),
team_id VARCHAR(36),
app_id VARCHAR(36),
proposal_id VARCHAR(36),
status ENUM('registered', 'submitted', 'approved', 'rejected') DEFAULT 'registered',
registered_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (competition_id) REFERENCES competitions(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
FOREIGN KEY (proposal_id) REFERENCES proposals(id) ON DELETE CASCADE,
INDEX idx_competition (competition_id),
INDEX idx_user (user_id),
INDEX idx_team (team_id),
INDEX idx_status (status)
)`,
// 16. 競賽評審分配表
`CREATE TABLE IF NOT EXISTS competition_judges (
id VARCHAR(36) PRIMARY KEY,
competition_id VARCHAR(36) NOT NULL,
judge_id VARCHAR(36) NOT NULL,
assigned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (competition_id) REFERENCES competitions(id) ON DELETE CASCADE,
FOREIGN KEY (judge_id) REFERENCES judges(id) ON DELETE CASCADE,
UNIQUE KEY unique_competition_judge (competition_id, judge_id),
INDEX idx_competition (competition_id),
INDEX idx_judge (judge_id)
)`,
// 17. 系統設定表
`CREATE TABLE IF NOT EXISTS system_settings (
id VARCHAR(36) PRIMARY KEY,
setting_key VARCHAR(100) UNIQUE NOT NULL,
setting_value TEXT,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX idx_key (setting_key)
)`,
// 18. 活動日誌表
`CREATE TABLE IF NOT EXISTS activity_logs (
id VARCHAR(36) PRIMARY KEY,
user_id VARCHAR(36),
action VARCHAR(100) NOT NULL,
target_type ENUM('user', 'competition', 'app', 'proposal', 'team', 'award') NOT NULL,
target_id VARCHAR(36),
details JSON,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL,
INDEX idx_user (user_id),
INDEX idx_action (action),
INDEX idx_target (target_type, target_id),
INDEX idx_created (created_at)
)`
];
console.log(`📋 準備執行 ${sqlStatements.length} 個SQL語句`);
for (let i = 0; i < sqlStatements.length; i++) {
const statement = sqlStatements[i];
try {
console.log(`執行語句 ${i + 1}/${sqlStatements.length}: ${statement.substring(0, 50)}...`);
await connection.query(statement);
} catch (error) {
console.error(`SQL執行錯誤 (語句 ${i + 1}):`, error.message);
}
}
// 5. 插入初始數據
console.log('\n📝 插入初始數據...');
const insertStatements = [
// 插入預設管理員用戶
`INSERT IGNORE INTO users (id, name, email, password_hash, department, role, join_date) VALUES
('admin-001', '系統管理員', 'admin@theaken.com', '$2b$10$rQZ8K9mN2pL1vX3yU7wE4tA6sB8cD1eF2gH3iJ4kL5mN6oP7qR8sT9uV0wX1yZ2a', '資訊部', 'admin', '2025-01-01')`,
// 插入預設評審
`INSERT IGNORE INTO judges (id, name, title, department, expertise) VALUES
('judge-001', '張教授', '資深技術顧問', '研發部', '["AI", "機器學習", "深度學習"]'),
('judge-002', '李經理', '產品經理', '產品部', '["產品設計", "用戶體驗", "市場分析"]'),
('judge-003', '王工程師', '資深工程師', '技術部', '["軟體開發", "系統架構", "雲端技術"]')`,
// 插入預設競賽
`INSERT IGNORE INTO competitions (id, name, year, month, start_date, end_date, status, description, type, evaluation_focus, max_team_size) VALUES
('comp-2025-01', '2025年AI創新競賽', 2025, 1, '2025-01-15', '2025-03-15', 'upcoming', '年度AI技術創新競賽鼓勵員工開發創新AI應用', 'mixed', '創新性、技術實現、實用價值', 5),
('comp-2025-02', '2025年提案競賽', 2025, 2, '2025-02-01', '2025-04-01', 'upcoming', 'AI解決方案提案競賽', 'proposal', '解決方案可行性、創新程度、商業價值', NULL)`,
// 插入AI助手配置
`INSERT IGNORE INTO ai_assistant_configs (id, api_key, api_url, model, max_tokens, temperature, system_prompt, is_active) VALUES
('ai-config-001', 'your_deepseek_api_key_here', 'https://api.deepseek.com/v1/chat/completions', 'deepseek-chat', 200, 0.7, '你是一個AI展示平台的智能助手專門協助用戶使用平台功能。請用友善、專業的態度回答問題。', TRUE)`,
// 插入系統設定
`INSERT IGNORE INTO system_settings (setting_key, setting_value, description) VALUES
('daily_like_limit', '5', '用戶每日按讚限制'),
('max_team_size', '5', '最大團隊人數'),
('competition_registration_deadline', '7', '競賽報名截止天數'),
('judge_score_weight_innovation', '25', '創新性評分權重'),
('judge_score_weight_technical', '25', '技術性評分權重'),
('judge_score_weight_usability', '20', '實用性評分權重'),
('judge_score_weight_presentation', '15', '展示效果評分權重'),
('judge_score_weight_impact', '15', '影響力評分權重')`
];
for (let i = 0; i < insertStatements.length; i++) {
const statement = insertStatements[i];
try {
console.log(`插入數據 ${i + 1}/${insertStatements.length}...`);
await connection.query(statement);
} catch (error) {
console.error(`插入數據錯誤 (語句 ${i + 1}):`, error.message);
}
}
console.log('✅ 資料庫建立完成!');
// 6. 驗證建立結果
console.log('\n📋 驗證資料庫結構...');
// 檢查資料表
const [tables] = await connection.query(`
SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.tables
WHERE table_schema = '${dbConfig.database}'
ORDER BY TABLE_NAME
`);
console.log('\n📊 資料表列表:');
console.log('─'.repeat(60));
console.log('表名'.padEnd(25) + '| 記錄數'.padEnd(10) + '| 狀態');
console.log('─'.repeat(60));
const expectedTables = [
'users', 'competitions', 'judges', 'teams', 'team_members',
'apps', 'proposals', 'judge_scores', 'awards', 'chat_sessions',
'chat_messages', 'ai_assistant_configs', 'user_favorites',
'user_likes', 'competition_participants', 'competition_judges',
'system_settings', 'activity_logs'
];
let successCount = 0;
for (const expectedTable of expectedTables) {
const table = tables.find(t => t.TABLE_NAME === expectedTable);
const status = table ? '✅' : '❌';
const rowCount = table ? (table.TABLE_ROWS || 0) : 'N/A';
console.log(`${expectedTable.padEnd(25)}| ${rowCount.toString().padEnd(10)}| ${status}`);
if (table) successCount++;
}
console.log(`\n📊 成功建立 ${successCount}/${expectedTables.length} 個資料表`);
// 檢查初始數據
console.log('\n📊 初始數據檢查:');
console.log('─'.repeat(40));
const checks = [
{ name: '管理員用戶', query: 'SELECT COUNT(*) as count FROM users WHERE role = "admin"' },
{ name: '預設評審', query: 'SELECT COUNT(*) as count FROM judges' },
{ name: '預設競賽', query: 'SELECT COUNT(*) as count FROM competitions' },
{ name: 'AI配置', query: 'SELECT COUNT(*) as count FROM ai_assistant_configs' },
{ name: '系統設定', query: 'SELECT COUNT(*) as count FROM system_settings' }
];
for (const check of checks) {
try {
const [result] = await connection.query(check.query);
console.log(`${check.name.padEnd(15)}: ${result[0].count}`);
} catch (error) {
console.log(`${check.name.padEnd(15)}: 查詢失敗 - ${error.message}`);
}
}
console.log('\n🎉 資料庫建立和驗證完成!');
console.log('\n📝 下一步:');
console.log('1. 複製 env.example 到 .env.local');
console.log('2. 設定環境變數');
console.log('3. 安裝依賴: pnpm install');
console.log('4. 啟動開發服務器: pnpm dev');
} catch (error) {
console.error('❌ 資料庫建立失敗:', error.message);
console.error('請檢查以下項目:');
console.error('1. 資料庫主機是否可達');
console.error('2. 用戶名和密碼是否正確');
console.error('3. 用戶是否有建立資料庫的權限');
process.exit(1);
} finally {
if (connection) {
await connection.end();
console.log('\n🔌 資料庫連接已關閉');
}
}
}
// 執行建立腳本
if (require.main === module) {
setupDatabase();
}
module.exports = { setupDatabase };

160
scripts/setup-database.js Normal file
View File

@@ -0,0 +1,160 @@
const fs = require('fs');
const path = require('path');
const mysql = require('mysql2/promise');
// 資料庫配置
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 setupDatabase() {
let connection;
try {
console.log('🚀 開始建立AI展示平台資料庫...');
console.log('─'.repeat(50));
// 1. 連接資料庫
console.log('🔌 連接資料庫...');
connection = await mysql.createConnection({
...dbConfig,
database: undefined // 先不指定資料庫,因為可能不存在
});
// 2. 檢查資料庫是否存在
const [databases] = await connection.query('SHOW DATABASES');
const dbExists = databases.some(db => db.Database === dbConfig.database);
if (!dbExists) {
console.log(`📊 建立資料庫: ${dbConfig.database}`);
await connection.query(`CREATE DATABASE ${dbConfig.database} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
} else {
console.log(`✅ 資料庫已存在: ${dbConfig.database}`);
}
// 3. 切換到目標資料庫
await connection.query(`USE ${dbConfig.database}`);
// 4. 讀取並執行SQL腳本
console.log('📝 執行資料庫建立腳本...');
const sqlScript = fs.readFileSync(path.join(__dirname, '../database_setup_simple.sql'), 'utf8');
// 分割SQL語句並執行
const statements = sqlScript
.split(';')
.map(stmt => stmt.trim())
.filter(stmt => stmt.length > 0 && !stmt.startsWith('--'))
.map(stmt => stmt + ';'); // 重新添加分號
console.log(`📋 找到 ${statements.length} 個SQL語句`);
for (let i = 0; i < statements.length; i++) {
const statement = statements[i];
if (statement.trim() && statement.trim() !== ';') {
try {
console.log(`執行語句 ${i + 1}/${statements.length}: ${statement.substring(0, 50)}...`);
await connection.query(statement);
} catch (error) {
// 忽略一些非關鍵錯誤(如表已存在等)
if (!error.message.includes('already exists') &&
!error.message.includes('Duplicate key') &&
!error.message.includes('Duplicate entry')) {
console.error(`SQL執行錯誤 (語句 ${i + 1}):`, error.message);
console.error('問題語句:', statement.substring(0, 100));
}
}
}
}
console.log('✅ 資料庫建立完成!');
// 5. 驗證建立結果
console.log('\n📋 驗證資料庫結構...');
// 檢查資料表
const [tables] = await connection.query(`
SELECT TABLE_NAME, TABLE_ROWS
FROM information_schema.tables
WHERE table_schema = '${dbConfig.database}'
ORDER BY TABLE_NAME
`);
console.log('\n📊 資料表列表:');
console.log('─'.repeat(60));
console.log('表名'.padEnd(25) + '| 記錄數'.padEnd(10) + '| 狀態');
console.log('─'.repeat(60));
const expectedTables = [
'users', 'competitions', 'judges', 'teams', 'team_members',
'apps', 'proposals', 'judge_scores', 'awards', 'chat_sessions',
'chat_messages', 'ai_assistant_configs', 'user_favorites',
'user_likes', 'competition_participants', 'competition_judges',
'system_settings', 'activity_logs'
];
let successCount = 0;
for (const expectedTable of expectedTables) {
const table = tables.find(t => t.TABLE_NAME === expectedTable);
const status = table ? '✅' : '❌';
const rowCount = table ? (table.TABLE_ROWS || 0) : 'N/A';
console.log(`${expectedTable.padEnd(25)}| ${rowCount.toString().padEnd(10)}| ${status}`);
if (table) successCount++;
}
console.log(`\n📊 成功建立 ${successCount}/${expectedTables.length} 個資料表`);
// 檢查初始數據
console.log('\n📊 初始數據檢查:');
console.log('─'.repeat(40));
const checks = [
{ name: '管理員用戶', query: 'SELECT COUNT(*) as count FROM users WHERE role = "admin"' },
{ name: '預設評審', query: 'SELECT COUNT(*) as count FROM judges' },
{ name: '預設競賽', query: 'SELECT COUNT(*) as count FROM competitions' },
{ name: 'AI配置', query: 'SELECT COUNT(*) as count FROM ai_assistant_configs' },
{ name: '系統設定', query: 'SELECT COUNT(*) as count FROM system_settings' }
];
for (const check of checks) {
try {
const [result] = await connection.query(check.query);
console.log(`${check.name.padEnd(15)}: ${result[0].count}`);
} catch (error) {
console.log(`${check.name.padEnd(15)}: 查詢失敗 - ${error.message}`);
}
}
console.log('\n🎉 資料庫建立和驗證完成!');
console.log('\n📝 下一步:');
console.log('1. 複製 env.example 到 .env.local');
console.log('2. 設定環境變數');
console.log('3. 安裝依賴: pnpm install');
console.log('4. 啟動開發服務器: pnpm dev');
} catch (error) {
console.error('❌ 資料庫建立失敗:', error.message);
console.error('請檢查以下項目:');
console.error('1. 資料庫主機是否可達');
console.error('2. 用戶名和密碼是否正確');
console.error('3. 用戶是否有建立資料庫的權限');
process.exit(1);
} finally {
if (connection) {
await connection.end();
console.log('\n🔌 資料庫連接已關閉');
}
}
}
// 執行建立腳本
if (require.main === module) {
setupDatabase();
}
module.exports = { setupDatabase };

80
scripts/simple-test.js Normal file
View File

@@ -0,0 +1,80 @@
const http = require('http');
// 簡單的 HTTP 請求函數
function makeSimpleRequest(url, method = 'GET', body = null) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
port: urlObj.port,
path: urlObj.pathname,
method: method,
headers: {
'Content-Type': 'application/json'
}
};
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const jsonData = JSON.parse(data);
resolve({
status: res.statusCode,
data: jsonData
});
} catch (error) {
resolve({
status: res.statusCode,
data: data
});
}
});
});
req.on('error', (error) => {
reject(error);
});
if (body) {
req.write(JSON.stringify(body));
}
req.end();
});
}
async function testSimple() {
console.log('🧪 簡單 API 測試...\n');
try {
// 測試健康檢查
console.log('1⃣ 測試健康檢查...');
const health = await makeSimpleRequest('http://localhost:3000/api');
console.log(` 狀態碼: ${health.status}`);
console.log(` 回應: ${JSON.stringify(health.data, null, 2)}`);
console.log('');
// 測試註冊 API
console.log('2⃣ 測試註冊 API...');
const register = await makeSimpleRequest('http://localhost:3000/api/auth/register', 'POST', {
name: '測試用戶',
email: 'test@example.com',
password: 'Test@2024',
department: '測試部門'
});
console.log(` 狀態碼: ${register.status}`);
console.log(` 回應: ${JSON.stringify(register.data, null, 2)}`);
} catch (error) {
console.error('❌ 測試失敗:', error.message);
}
}
testSimple();

154
scripts/test-api.js Normal file
View File

@@ -0,0 +1,154 @@
const https = require('https');
const http = require('http');
// 測試配置
const BASE_URL = 'http://localhost:3000';
const ADMIN_EMAIL = 'admin@theaken.com';
const ADMIN_PASSWORD = 'Admin@2024';
// 測試用戶資料
const TEST_USER = {
name: '測試用戶',
email: 'test@theaken.com',
password: 'Test@2024',
department: '測試部門',
role: 'user'
};
// 發送 HTTP 請求
async function makeRequest(url, options = {}) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const isHttps = urlObj.protocol === 'https:';
const client = isHttps ? https : http;
const requestOptions = {
hostname: urlObj.hostname,
port: urlObj.port,
path: urlObj.pathname + urlObj.search,
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
...options.headers
}
};
const req = client.request(requestOptions, (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
const jsonData = JSON.parse(data);
resolve({
status: res.statusCode,
headers: res.headers,
data: jsonData
});
} catch (error) {
resolve({
status: res.statusCode,
headers: res.headers,
data: data
});
}
});
});
req.on('error', (error) => {
reject(error);
});
if (options.body) {
req.write(JSON.stringify(options.body));
}
req.end();
});
}
// 測試函數
async function testAPI() {
console.log('🧪 開始測試 API...\n');
try {
// 1. 測試健康檢查
console.log('1⃣ 測試健康檢查 API...');
const healthResponse = await makeRequest(`${BASE_URL}/api`);
console.log(` 狀態碼: ${healthResponse.status}`);
console.log(` 回應: ${JSON.stringify(healthResponse.data, null, 2)}`);
console.log('');
// 2. 測試註冊 API
console.log('2⃣ 測試註冊 API...');
const registerResponse = await makeRequest(`${BASE_URL}/api/auth/register`, {
method: 'POST',
body: TEST_USER
});
console.log(` 狀態碼: ${registerResponse.status}`);
console.log(` 回應: ${JSON.stringify(registerResponse.data, null, 2)}`);
console.log('');
// 3. 測試登入 API
console.log('3⃣ 測試登入 API...');
const loginResponse = await makeRequest(`${BASE_URL}/api/auth/login`, {
method: 'POST',
body: {
email: ADMIN_EMAIL,
password: ADMIN_PASSWORD
}
});
console.log(` 狀態碼: ${loginResponse.status}`);
let authToken = null;
if (loginResponse.status === 200) {
authToken = loginResponse.data.token;
console.log(` 登入成功,獲得 Token`);
} else {
console.log(` 登入失敗: ${JSON.stringify(loginResponse.data, null, 2)}`);
}
console.log('');
// 4. 測試獲取當前用戶 API
if (authToken) {
console.log('4⃣ 測試獲取當前用戶 API...');
const meResponse = await makeRequest(`${BASE_URL}/api/auth/me`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
console.log(` 狀態碼: ${meResponse.status}`);
console.log(` 回應: ${JSON.stringify(meResponse.data, null, 2)}`);
console.log('');
}
// 5. 測試用戶列表 API (需要管理員權限)
if (authToken) {
console.log('5⃣ 測試用戶列表 API...');
const usersResponse = await makeRequest(`${BASE_URL}/api/users`, {
headers: {
'Authorization': `Bearer ${authToken}`
}
});
console.log(` 狀態碼: ${usersResponse.status}`);
console.log(` 回應: ${JSON.stringify(usersResponse.data, null, 2)}`);
console.log('');
}
console.log('✅ API 測試完成!');
} catch (error) {
console.error('❌ API 測試失敗:', error.message);
console.error('錯誤詳情:', error);
}
}
// 執行測試
if (require.main === module) {
testAPI();
}
module.exports = { testAPI };