// ===================================================== // 資料庫服務層 // ===================================================== import { db } from '../database'; import type { User, Judge, Team, TeamMember, Competition, CompetitionRule, CompetitionAwardType, App, Proposal, AppJudgeScore, ProposalJudgeScore, Award, UserFavorite, UserLike, UserView, UserRating, ChatSession, ChatMessage, AIAssistantConfig, SystemSetting, ActivityLog, UserStatistics, AppStatistics, CompetitionStatistics } from '../models'; // ===================================================== // 用戶服務 // ===================================================== export class UserService { // 創建用戶 async create(userData: Omit): Promise { const sql = ` INSERT INTO users (id, name, email, password_hash, avatar, department, role, join_date, total_likes, total_views, is_active, last_login) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ userData.id, userData.name, userData.email, userData.password_hash, userData.avatar || null, userData.department, userData.role, userData.join_date, userData.total_likes, userData.total_views, userData.is_active, userData.last_login || null ]; await db.insert(sql, params); return await this.findByEmail(userData.email) as User; } // 根據郵箱獲取用戶 async findByEmail(email: string): Promise { const sql = 'SELECT * FROM users WHERE email = ? AND is_active = TRUE'; return await db.queryOne(sql, [email]); } // 根據ID獲取用戶 async findById(id: string): Promise { const sql = 'SELECT * FROM users WHERE id = ? AND is_active = TRUE'; return await db.queryOne(sql, [id]); } // 更新用戶 async update(id: string, updates: Partial): Promise { const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at'); const setClause = fields.map(field => `${field} = ?`).join(', '); const values = fields.map(field => (updates as any)[field]); const sql = `UPDATE users SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; const result = await db.update(sql, [...values, id]); if (result.affectedRows > 0) { return await this.findById(id); } return null; } // 更新最後登入時間 async updateLastLogin(id: string): Promise { const sql = 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?'; const result = await db.update(sql, [id]); return result.affectedRows > 0; } // 獲取用戶統計 async getUserStatistics(id: string): Promise { const sql = 'SELECT * FROM user_statistics WHERE id = ?'; return await db.queryOne(sql, [id]); } // 獲取所有用戶(管理員用) async findAll(filters: { search?: string; department?: string; role?: string; status?: string; page?: number; limit?: number; } = {}): Promise<{ users: User[]; total: number }> { const { search, department, role, status, page = 1, limit = 10 } = filters; // 構建查詢條件 let whereConditions = ['is_active = TRUE']; let params: any[] = []; if (search) { whereConditions.push('(name LIKE ? OR email LIKE ?)'); params.push(`%${search}%`, `%${search}%`); } if (department && department !== 'all') { whereConditions.push('department = ?'); params.push(department); } if (role && role !== 'all') { whereConditions.push('role = ?'); params.push(role); } if (status && status !== 'all') { if (status === 'active') { whereConditions.push('last_login IS NOT NULL AND last_login >= DATE_SUB(NOW(), INTERVAL 30 DAY)'); } else if (status === 'inactive') { whereConditions.push('last_login IS NULL OR last_login < DATE_SUB(NOW(), INTERVAL 30 DAY)'); } } const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : ''; // 獲取總數 const countSql = `SELECT COUNT(*) as total FROM users ${whereClause}`; const countResult = await this.query(countSql, params); const total = countResult[0]?.total || 0; // 獲取用戶列表 const offset = (page - 1) * limit; const usersSql = ` SELECT id, name, email, avatar, department, role, join_date, total_likes, total_views, is_active, last_login, created_at, updated_at FROM users ${whereClause} ORDER BY created_at DESC LIMIT ${offset}, ${limit} `; const users = await this.query(usersSql, params); return { users, total }; } // 獲取用戶統計數據 async getUserStats(): Promise<{ totalUsers: number; activeUsers: number; adminCount: number; developerCount: number; inactiveUsers: number; newThisMonth: number; }> { const sql = ` SELECT COUNT(*) as total_users, COUNT(CASE WHEN last_login IS NOT NULL AND last_login >= DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as active_users, COUNT(CASE WHEN role = 'admin' THEN 1 END) as admin_count, COUNT(CASE WHEN role = 'developer' THEN 1 END) as developer_count, COUNT(CASE WHEN last_login IS NULL OR last_login < DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as inactive_users, COUNT(CASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as new_this_month FROM users WHERE is_active = TRUE `; const result = await this.query(sql); const stats = result[0] || {}; return { totalUsers: stats.total_users || 0, activeUsers: stats.active_users || 0, adminCount: stats.admin_count || 0, developerCount: stats.developer_count || 0, inactiveUsers: stats.inactive_users || 0, newThisMonth: stats.new_this_month || 0 }; } // 通用查詢方法 async query(sql: string, params: any[] = []): Promise { return await db.query(sql, params); } // 通用單一查詢方法 async queryOne(sql: string, params: any[] = []): Promise { return await db.queryOne(sql, params); } // 獲取所有用戶 async getAllUsers(limit = 50, offset = 0): Promise { const sql = 'SELECT * FROM users WHERE is_active = TRUE ORDER BY created_at DESC LIMIT ? OFFSET ?'; return await db.query(sql, [limit, offset]); } // 靜態方法保持向後兼容 static async createUser(userData: Omit): Promise { const service = new UserService(); return await service.create(userData); } static async getUserByEmail(email: string): Promise { const service = new UserService(); return await service.findByEmail(email); } static async getUserById(id: string): Promise { const service = new UserService(); return await service.findById(id); } static async updateUser(id: string, updates: Partial): Promise { const service = new UserService(); const result = await service.update(id, updates); return result !== null; } static async getUserStatistics(id: string): Promise { const service = new UserService(); return await service.getUserStatistics(id); } static async getAllUsers(limit = 50, offset = 0): Promise { const service = new UserService(); return await service.getAllUsers(limit, offset); } } // ===================================================== // 評審服務 // ===================================================== export class JudgeService { // 創建評審 static async createJudge(judgeData: Omit): Promise { const sql = ` INSERT INTO judges (id, name, title, department, expertise, avatar, is_active) VALUES (UUID(), ?, ?, ?, ?, ?, ?) `; const params = [ judgeData.name, judgeData.title, judgeData.department, JSON.stringify(judgeData.expertise), judgeData.avatar || null, judgeData.is_active ]; await db.insert(sql, params); return await this.getJudgeByName(judgeData.name) as Judge; } // 根據姓名獲取評審 static async getJudgeByName(name: string): Promise { const sql = 'SELECT * FROM judges WHERE name = ? AND is_active = TRUE'; const result = await db.queryOne(sql, [name]); if (result) { result.expertise = JSON.parse(result.expertise as any); } return result; } // 根據ID獲取評審 static async getJudgeById(id: string): Promise { const sql = 'SELECT * FROM judges WHERE id = ? AND is_active = TRUE'; const result = await db.queryOne(sql, [id]); if (result) { result.expertise = JSON.parse(result.expertise as any); } return result; } // 獲取所有評審 static async getAllJudges(): Promise { const sql = 'SELECT * FROM judges WHERE is_active = TRUE ORDER BY created_at DESC'; const results = await db.query(sql); return results.map(judge => ({ ...judge, expertise: JSON.parse(judge.expertise as any) })); } // 更新評審 static async updateJudge(id: string, updates: Partial): Promise { const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at'); const setClause = fields.map(field => `${field} = ?`).join(', '); const values = fields.map(field => { if (field === 'expertise') { return JSON.stringify((updates as any)[field]); } return (updates as any)[field]; }); const sql = `UPDATE judges SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; const result = await db.update(sql, [...values, id]); return result.affectedRows > 0; } } // ===================================================== // 競賽服務 // ===================================================== export class CompetitionService { // 創建競賽 static async createCompetition(competitionData: Omit): Promise { const sql = ` INSERT INTO competitions (id, name, year, month, start_date, end_date, status, description, type, evaluation_focus, max_team_size, is_active) VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ competitionData.name, competitionData.year, competitionData.month, competitionData.start_date, competitionData.end_date, competitionData.status, competitionData.description || null, competitionData.type, competitionData.evaluation_focus || null, competitionData.max_team_size || null, competitionData.is_active ]; await db.insert(sql, params); return await this.getCompetitionByName(competitionData.name) as Competition; } // 根據名稱獲取競賽 static async getCompetitionByName(name: string): Promise { const sql = 'SELECT * FROM competitions WHERE name = ? AND is_active = TRUE'; return await db.queryOne(sql, [name]); } // 根據ID獲取競賽 static async getCompetitionById(id: string): Promise { const sql = 'SELECT * FROM competitions WHERE id = ? AND is_active = TRUE'; return await db.queryOne(sql, [id]); } // 獲取所有競賽 static async getAllCompetitions(): Promise { const sql = 'SELECT * FROM competitions WHERE is_active = TRUE ORDER BY year DESC, month DESC'; return await db.query(sql); } // 獲取競賽統計 static async getCompetitionStatistics(id: string): Promise { const sql = 'SELECT * FROM competition_statistics WHERE id = ?'; return await db.queryOne(sql, [id]); } // 更新競賽 static async updateCompetition(id: string, updates: Partial): Promise { const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at'); const setClause = fields.map(field => `${field} = ?`).join(', '); const values = fields.map(field => (updates as any)[field]); const sql = `UPDATE competitions SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; const result = await db.update(sql, [...values, id]); return result.affectedRows > 0; } } // ===================================================== // 應用服務 // ===================================================== export class AppService { // 創建應用 static async createApp(appData: Omit): Promise { const sql = ` INSERT INTO apps (id, name, description, creator_id, team_id, category, type, likes_count, views_count, rating, is_active) VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ appData.name, appData.description || null, appData.creator_id, appData.team_id || null, appData.category, appData.type, appData.likes_count, appData.views_count, appData.rating, appData.is_active ]; await db.insert(sql, params); return await this.getAppByName(appData.name) as App; } // 根據名稱獲取應用 static async getAppByName(name: string): Promise { const sql = 'SELECT * FROM apps WHERE name = ? AND is_active = TRUE'; return await db.queryOne(sql, [name]); } // 根據ID獲取應用 static async getAppById(id: string): Promise { const sql = 'SELECT * FROM apps WHERE id = ? AND is_active = TRUE'; return await db.queryOne(sql, [id]); } // 獲取所有應用 static async getAllApps(limit = 50, offset = 0): Promise { const sql = 'SELECT * FROM apps WHERE is_active = TRUE ORDER BY created_at DESC LIMIT ? OFFSET ?'; return await db.query(sql, [limit, offset]); } // 獲取應用統計 static async getAppStatistics(id: string): Promise { const sql = 'SELECT * FROM app_statistics WHERE id = ?'; return await db.queryOne(sql, [id]); } // 更新應用 static async updateApp(id: string, updates: Partial): Promise { const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at'); const setClause = fields.map(field => `${field} = ?`).join(', '); const values = fields.map(field => (updates as any)[field]); const sql = `UPDATE apps SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`; const result = await db.update(sql, [...values, id]); return result.affectedRows > 0; } } // ===================================================== // 評分服務 // ===================================================== export class ScoringService { // 提交應用評分 static async submitAppScore(scoreData: Omit): Promise { const sql = ` INSERT INTO app_judge_scores (id, judge_id, app_id, innovation_score, technical_score, usability_score, presentation_score, impact_score, total_score, comments) VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE innovation_score = VALUES(innovation_score), technical_score = VALUES(technical_score), usability_score = VALUES(usability_score), presentation_score = VALUES(presentation_score), impact_score = VALUES(impact_score), total_score = VALUES(total_score), comments = VALUES(comments), submitted_at = CURRENT_TIMESTAMP `; const params = [ scoreData.judge_id, scoreData.app_id, scoreData.innovation_score, scoreData.technical_score, scoreData.usability_score, scoreData.presentation_score, scoreData.impact_score, scoreData.total_score, scoreData.comments || null ]; await db.insert(sql, params); return await this.getAppScore(scoreData.judge_id, scoreData.app_id) as AppJudgeScore; } // 獲取應用評分 static async getAppScore(judgeId: string, appId: string): Promise { const sql = 'SELECT * FROM app_judge_scores WHERE judge_id = ? AND app_id = ?'; return await db.queryOne(sql, [judgeId, appId]); } // 獲取應用的所有評分 static async getAppScores(appId: string): Promise { const sql = 'SELECT * FROM app_judge_scores WHERE app_id = ? ORDER BY submitted_at DESC'; return await db.query(sql, [appId]); } // 提交提案評分 static async submitProposalScore(scoreData: Omit): Promise { const sql = ` INSERT INTO proposal_judge_scores (id, judge_id, proposal_id, problem_identification_score, solution_feasibility_score, innovation_score, impact_score, presentation_score, total_score, comments) VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE problem_identification_score = VALUES(problem_identification_score), solution_feasibility_score = VALUES(solution_feasibility_score), innovation_score = VALUES(innovation_score), impact_score = VALUES(impact_score), presentation_score = VALUES(presentation_score), total_score = VALUES(total_score), comments = VALUES(comments), submitted_at = CURRENT_TIMESTAMP `; const params = [ scoreData.judge_id, scoreData.proposal_id, scoreData.problem_identification_score, scoreData.solution_feasibility_score, scoreData.innovation_score, scoreData.impact_score, scoreData.presentation_score, scoreData.total_score, scoreData.comments || null ]; await db.insert(sql, params); return await this.getProposalScore(scoreData.judge_id, scoreData.proposal_id) as ProposalJudgeScore; } // 獲取提案評分 static async getProposalScore(judgeId: string, proposalId: string): Promise { const sql = 'SELECT * FROM proposal_judge_scores WHERE judge_id = ? AND proposal_id = ?'; return await db.queryOne(sql, [judgeId, proposalId]); } // 獲取提案的所有評分 static async getProposalScores(proposalId: string): Promise { const sql = 'SELECT * FROM proposal_judge_scores WHERE proposal_id = ? ORDER BY submitted_at DESC'; return await db.query(sql, [proposalId]); } } // ===================================================== // 獎項服務 // ===================================================== export class AwardService { // 創建獎項 static async createAward(awardData: Omit): Promise { const sql = ` INSERT INTO awards (id, competition_id, app_id, team_id, proposal_id, app_name, team_name, proposal_title, creator, award_type, award_name, score, year, month, icon, custom_award_type_id, competition_type, rank, category) VALUES (UUID(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const params = [ awardData.competition_id, awardData.app_id || null, awardData.team_id || null, awardData.proposal_id || null, awardData.app_name || null, awardData.team_name || null, awardData.proposal_title || null, awardData.creator, awardData.award_type, awardData.award_name, awardData.score, awardData.year, awardData.month, awardData.icon, awardData.custom_award_type_id || null, awardData.competition_type, awardData.rank, awardData.category ]; await db.insert(sql, params); return await this.getAwardByCompetitionAndCreator(awardData.competition_id, awardData.creator) as Award; } // 根據競賽和創作者獲取獎項 static async getAwardByCompetitionAndCreator(competitionId: string, creator: string): Promise { const sql = 'SELECT * FROM awards WHERE competition_id = ? AND creator = ? ORDER BY created_at DESC LIMIT 1'; return await db.queryOne(sql, [competitionId, creator]); } // 根據年份獲取獎項 static async getAwardsByYear(year: number): Promise { const sql = 'SELECT * FROM awards WHERE year = ? ORDER BY month DESC, rank ASC'; return await db.query(sql, [year]); } // 根據競賽獲取獎項 static async getAwardsByCompetition(competitionId: string): Promise { const sql = 'SELECT * FROM awards WHERE competition_id = ? ORDER BY rank ASC'; return await db.query(sql, [competitionId]); } } // ===================================================== // 系統設定服務 // ===================================================== export class SystemSettingService { // 獲取設定值 static async getSetting(key: string): Promise { const sql = 'SELECT value FROM system_settings WHERE `key` = ?'; const result = await db.queryOne<{ value: string }>(sql, [key]); return result?.value || null; } // 設定值 static async setSetting(key: string, value: string, description?: string, category = 'general', isPublic = false): Promise { const sql = ` INSERT INTO system_settings (id, \`key\`, value, description, category, is_public) VALUES (UUID(), ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), description = VALUES(description), category = VALUES(category), is_public = VALUES(is_public), updated_at = CURRENT_TIMESTAMP `; const params = [key, value, description || null, category, isPublic]; const result = await db.insert(sql, params); return result.affectedRows > 0; } // 獲取所有公開設定 static async getPublicSettings(): Promise { const sql = 'SELECT * FROM system_settings WHERE is_public = TRUE ORDER BY category, `key`'; return await db.query(sql); } }