import { query, transaction } from '../database'; import type { User, CriteriaTemplate, CriteriaItem, Project, ProjectFile, ProjectWebsite, Evaluation, EvaluationScore, EvaluationFeedback, ProjectWithDetails, EvaluationWithDetails, CriteriaTemplateWithItems, } from '../models'; // 用戶相關操作 export class UserService { static async create(userData: Omit) { const sql = ` INSERT INTO users (email, username, password_hash, full_name, avatar_url, role, is_active, email_verified_at, last_login_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ userData.email, userData.username, userData.password_hash, userData.full_name, userData.avatar_url, userData.role, userData.is_active, userData.email_verified_at, userData.last_login_at, ]); return result; } static async findByEmail(email: string): Promise { const sql = 'SELECT * FROM users WHERE email = ?'; const rows = await query(sql, [email]) as User[]; return rows.length > 0 ? rows[0] : null; } static async findById(id: number): Promise { const sql = 'SELECT * FROM users WHERE id = ?'; const rows = await query(sql, [id]) as User[]; return rows.length > 0 ? rows[0] : null; } static async updateLastLogin(id: number) { const sql = 'UPDATE users SET last_login_at = NOW() WHERE id = ?'; await query(sql, [id]); } } // 評分標準模板相關操作 export class CriteriaTemplateService { static async create(templateData: Omit) { const sql = ` INSERT INTO criteria_templates (user_id, name, description, is_default, is_public, total_weight) VALUES (?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ templateData.user_id, templateData.name, templateData.description, templateData.is_default, templateData.is_public, templateData.total_weight, ]); return result; } static async findById(id: number): Promise { const sql = 'SELECT * FROM criteria_templates WHERE id = ?'; const rows = await query(sql, [id]) as CriteriaTemplate[]; return rows.length > 0 ? rows[0] : null; } static async findByUserId(userId: number): Promise { const sql = 'SELECT * FROM criteria_templates WHERE user_id = ? ORDER BY created_at DESC'; return await query(sql, [userId]) as CriteriaTemplate[]; } static async findDefault(): Promise { const sql = 'SELECT * FROM criteria_templates WHERE is_default = 1 LIMIT 1'; const rows = await query(sql) as CriteriaTemplate[]; return rows.length > 0 ? rows[0] : null; } static async findWithItems(id: number): Promise { const template = await this.findById(id); if (!template) return null; const itemsSql = 'SELECT * FROM criteria_items WHERE template_id = ? ORDER BY sort_order'; const items = await query(itemsSql, [id]) as CriteriaItem[]; return { ...template, items }; } static async update(id: number, templateData: Partial) { const fields = Object.keys(templateData).map(key => `${key} = ?`).join(', '); const values = Object.values(templateData); const sql = `UPDATE criteria_templates SET ${fields}, updated_at = NOW() WHERE id = ?`; await query(sql, [...values, id]); } static async delete(id: number) { const sql = 'DELETE FROM criteria_templates WHERE id = ?'; await query(sql, [id]); } } // 評分項目相關操作 export class CriteriaItemService { static async create(itemData: Omit) { const sql = ` INSERT INTO criteria_items (template_id, name, description, weight, max_score, sort_order) VALUES (?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ itemData.template_id, itemData.name, itemData.description, itemData.weight, itemData.max_score, itemData.sort_order, ]); return result; } static async getAllTemplates(): Promise { const sql = ` SELECT t.*, JSON_ARRAYAGG( JSON_OBJECT( 'id', i.id, 'name', i.name, 'description', i.description, 'weight', i.weight, 'maxScore', i.max_score, 'sort_order', i.sort_order ) ) as items FROM criteria_templates t LEFT JOIN criteria_items i ON t.id = i.template_id GROUP BY t.id ORDER BY t.created_at DESC `; const rows = await query(sql) as any[]; return rows.map(row => { let items = []; if (row.items) { try { // 檢查 items 是否已經是對象數組 if (Array.isArray(row.items)) { items = row.items; } else if (typeof row.items === 'string') { items = JSON.parse(row.items); } else { console.log('items 類型:', typeof row.items, row.items); items = []; } // 手動排序項目 items.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); } catch (error) { console.error('解析 items JSON 失敗:', error); console.error('原始 items 數據:', row.items); items = []; } } return { ...row, items }; }); } static async findByTemplateId(templateId: number): Promise { const sql = 'SELECT * FROM criteria_items WHERE template_id = ? ORDER BY sort_order'; const rows = await query(sql, [templateId]) as any[]; // 映射資料庫欄位到前端期望的格式 return rows.map(row => ({ id: row.id, template_id: row.template_id, name: row.name, description: row.description, weight: Number(row.weight) || 0, maxScore: Number(row.max_score) || 10, // 映射 max_score 到 maxScore 並轉換為數字 sort_order: row.sort_order, created_at: row.created_at, updated_at: row.updated_at })); } static async update(id: number, itemData: Partial) { const fields = Object.keys(itemData).map(key => `${key} = ?`).join(', '); const values = Object.values(itemData); const sql = `UPDATE criteria_items SET ${fields}, updated_at = NOW() WHERE id = ?`; await query(sql, [...values, id]); } static async delete(id: number) { const sql = 'DELETE FROM criteria_items WHERE id = ?'; await query(sql, [id]); } static async deleteByTemplateId(templateId: number) { const sql = 'DELETE FROM criteria_items WHERE template_id = ?'; await query(sql, [templateId]); } } // 專案相關操作 export class ProjectService { static async create(projectData: Omit) { const sql = ` INSERT INTO projects (user_id, template_id, title, description, status, analysis_started_at, analysis_completed_at) VALUES (?, ?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ projectData.user_id, projectData.template_id, projectData.title, projectData.description ?? null, projectData.status, projectData.analysis_started_at ?? null, projectData.analysis_completed_at ?? null, ]); return result; } static async findById(id: number): Promise { const sql = 'SELECT * FROM projects WHERE id = ?'; const rows = await query(sql, [id]) as Project[]; return rows.length > 0 ? rows[0] : null; } static async findByUserId(userId: number, limit = 20, offset = 0): Promise { const sql = ` SELECT * FROM projects WHERE user_id = ? ORDER BY created_at DESC LIMIT ? OFFSET ? `; return await query(sql, [userId, limit, offset]) as Project[]; } static async findWithDetails(id: number): Promise { const project = await this.findById(id); if (!project) return null; const template = await CriteriaTemplateService.findById(project.template_id); if (!template) return null; const files = await ProjectFileService.findByProjectId(id); const websites = await ProjectWebsiteService.findByProjectId(id); const evaluation = await EvaluationService.findByProjectId(id); return { ...project, template, files, websites, evaluation, }; } static async update(id: number, projectData: Partial) { const fields = Object.keys(projectData).map(key => `${key} = ?`).join(', '); const values = Object.values(projectData); const sql = `UPDATE projects SET ${fields}, updated_at = NOW() WHERE id = ?`; await query(sql, [...values, id]); } static async delete(id: number) { const sql = 'DELETE FROM projects WHERE id = ?'; await query(sql, [id]); } } // 專案文件相關操作 export class ProjectFileService { static async create(fileData: Omit) { const sql = ` INSERT INTO project_files (project_id, original_name, file_name, file_path, file_size, file_type, mime_type, upload_status, upload_progress) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ fileData.project_id, fileData.original_name, fileData.file_name, fileData.file_path, fileData.file_size, fileData.file_type, fileData.mime_type, fileData.upload_status, fileData.upload_progress, ]); return result; } static async findByProjectId(projectId: number): Promise { const sql = 'SELECT * FROM project_files WHERE project_id = ? ORDER BY created_at'; return await query(sql, [projectId]) as ProjectFile[]; } static async updateStatus(id: number, status: ProjectFile['upload_status'], progress?: number) { const sql = 'UPDATE project_files SET upload_status = ?, upload_progress = ?, updated_at = NOW() WHERE id = ?'; await query(sql, [status, progress || 0, id]); } static async delete(id: number) { const sql = 'DELETE FROM project_files WHERE id = ?'; await query(sql, [id]); } } // 專案網站相關操作 export class ProjectWebsiteService { static async create(websiteData: Omit) { const sql = ` INSERT INTO project_websites (project_id, url, title, description, status) VALUES (?, ?, ?, ?, ?) `; const result = await query(sql, [ websiteData.project_id, websiteData.url, websiteData.title, websiteData.description, websiteData.status, ]); return result; } static async findByProjectId(projectId: number): Promise { const sql = 'SELECT * FROM project_websites WHERE project_id = ? ORDER BY created_at'; return await query(sql, [projectId]) as ProjectWebsite[]; } static async updateStatus(id: number, status: ProjectWebsite['status']) { const sql = 'UPDATE project_websites SET status = ?, updated_at = NOW() WHERE id = ?'; await query(sql, [status, id]); } static async delete(id: number) { const sql = 'DELETE FROM project_websites WHERE id = ?'; await query(sql, [id]); } } // 評審相關操作 export class EvaluationService { static async create(evaluationData: Omit) { const sql = ` INSERT INTO evaluations (project_id, overall_score, max_possible_score, grade, performance_status, recommended_stars, excellent_items, improvement_items, analysis_duration, ai_model_version, status, error_message) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ evaluationData.project_id, evaluationData.overall_score ?? null, evaluationData.max_possible_score, evaluationData.grade ?? null, evaluationData.performance_status ?? null, evaluationData.recommended_stars ?? null, evaluationData.excellent_items ?? null, evaluationData.improvement_items ?? null, evaluationData.analysis_duration ?? null, evaluationData.ai_model_version ?? null, evaluationData.status, evaluationData.error_message ?? null, ]); return result; } static async findById(id: number): Promise { const sql = 'SELECT * FROM evaluations WHERE id = ?'; const rows = await query(sql, [id]) as Evaluation[]; return rows.length > 0 ? rows[0] : null; } static async findByProjectId(projectId: number): Promise { const sql = 'SELECT * FROM evaluations WHERE project_id = ? ORDER BY created_at DESC LIMIT 1'; const rows = await query(sql, [projectId]) as Evaluation[]; return rows.length > 0 ? rows[0] : null; } static async findWithDetails(id: number): Promise { const evaluation = await this.findById(id); if (!evaluation) return null; const project = await ProjectService.findById(evaluation.project_id); if (!project) return null; const scoresSql = ` SELECT es.*, ci.name as criteria_item_name, ci.description as criteria_item_description FROM evaluation_scores es JOIN criteria_items ci ON es.criteria_item_id = ci.id WHERE es.evaluation_id = ? ORDER BY ci.sort_order `; const scores = await query(scoresSql, [id]) as (EvaluationScore & { criteria_item: CriteriaItem })[]; const feedback = await EvaluationFeedbackService.findByEvaluationId(id); return { ...evaluation, project, scores, feedback, }; } static async update(id: number, evaluationData: Partial) { const fields = Object.keys(evaluationData).map(key => `${key} = ?`).join(', '); const values = Object.values(evaluationData); const sql = `UPDATE evaluations SET ${fields}, updated_at = NOW() WHERE id = ?`; await query(sql, [...values, id]); } static async delete(id: number) { const sql = 'DELETE FROM evaluations WHERE id = ?'; await query(sql, [id]); } } // 評分結果相關操作 export class EvaluationScoreService { static async create(scoreData: Omit) { const sql = ` INSERT INTO evaluation_scores (evaluation_id, criteria_item_id, score, max_score, weight, weighted_score, percentage) VALUES (?, ?, ?, ?, ?, ?, ?) `; const result = await query(sql, [ scoreData.evaluation_id ?? null, scoreData.criteria_item_id ?? null, scoreData.score ?? null, scoreData.max_score ?? null, scoreData.weight ?? null, scoreData.weighted_score ?? null, scoreData.percentage ?? null, ]); return result; } static async findByEvaluationId(evaluationId: number): Promise { const sql = 'SELECT * FROM evaluation_scores WHERE evaluation_id = ?'; return await query(sql, [evaluationId]) as EvaluationScore[]; } static async deleteByEvaluationId(evaluationId: number) { const sql = 'DELETE FROM evaluation_scores WHERE evaluation_id = ?'; await query(sql, [evaluationId]); } } // 評語相關操作 export class EvaluationFeedbackService { static async create(feedbackData: Omit) { const sql = ` INSERT INTO evaluation_feedback (evaluation_id, criteria_item_id, feedback_type, content, sort_order) VALUES (?, ?, ?, ?, ?) `; const result = await query(sql, [ feedbackData.evaluation_id, feedbackData.criteria_item_id ?? null, feedbackData.feedback_type, feedbackData.content, feedbackData.sort_order, ]); return result; } static async findByEvaluationId(evaluationId: number): Promise { const sql = 'SELECT * FROM evaluation_feedback WHERE evaluation_id = ? ORDER BY sort_order'; return await query(sql, [evaluationId]) as EvaluationFeedback[]; } static async deleteByEvaluationId(evaluationId: number) { const sql = 'DELETE FROM evaluation_feedback WHERE evaluation_id = ?'; await query(sql, [evaluationId]); } }