434 lines
14 KiB
TypeScript
434 lines
14 KiB
TypeScript
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<User, 'id' | 'created_at' | 'updated_at'>) {
|
|
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<User | null> {
|
|
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<User | null> {
|
|
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<CriteriaTemplate, 'id' | 'created_at' | 'updated_at'>) {
|
|
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<CriteriaTemplate | null> {
|
|
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<CriteriaTemplate[]> {
|
|
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<CriteriaTemplate | null> {
|
|
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<CriteriaTemplateWithItems | null> {
|
|
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<CriteriaTemplate>) {
|
|
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<CriteriaItem, 'id' | 'created_at' | 'updated_at'>) {
|
|
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 findByTemplateId(templateId: number): Promise<CriteriaItem[]> {
|
|
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<CriteriaItem>) {
|
|
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<Project, 'id' | 'created_at' | 'updated_at'>) {
|
|
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,
|
|
projectData.status,
|
|
projectData.analysis_started_at,
|
|
projectData.analysis_completed_at,
|
|
]);
|
|
return result;
|
|
}
|
|
|
|
static async findById(id: number): Promise<Project | null> {
|
|
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<Project[]> {
|
|
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<ProjectWithDetails | null> {
|
|
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<Project>) {
|
|
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<ProjectFile, 'id' | 'created_at' | 'updated_at'>) {
|
|
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<ProjectFile[]> {
|
|
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<ProjectWebsite, 'id' | 'created_at' | 'updated_at'>) {
|
|
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<ProjectWebsite[]> {
|
|
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<Evaluation, 'id' | 'created_at' | 'updated_at'>) {
|
|
const sql = `
|
|
INSERT INTO evaluations (project_id, overall_score, max_possible_score, grade, analysis_duration, ai_model_version, status, error_message)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
`;
|
|
const result = await query(sql, [
|
|
evaluationData.project_id,
|
|
evaluationData.overall_score,
|
|
evaluationData.max_possible_score,
|
|
evaluationData.grade,
|
|
evaluationData.analysis_duration,
|
|
evaluationData.ai_model_version,
|
|
evaluationData.status,
|
|
evaluationData.error_message,
|
|
]);
|
|
return result;
|
|
}
|
|
|
|
static async findById(id: number): Promise<Evaluation | null> {
|
|
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<Evaluation | null> {
|
|
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<EvaluationWithDetails | null> {
|
|
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<Evaluation>) {
|
|
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<EvaluationScore, 'id' | 'created_at'>) {
|
|
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,
|
|
scoreData.criteria_item_id,
|
|
scoreData.score,
|
|
scoreData.max_score,
|
|
scoreData.weight,
|
|
scoreData.weighted_score,
|
|
scoreData.percentage,
|
|
]);
|
|
return result;
|
|
}
|
|
|
|
static async findByEvaluationId(evaluationId: number): Promise<EvaluationScore[]> {
|
|
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<EvaluationFeedback, 'id' | 'created_at'>) {
|
|
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,
|
|
feedbackData.feedback_type,
|
|
feedbackData.content,
|
|
feedbackData.sort_order,
|
|
]);
|
|
return result;
|
|
}
|
|
|
|
static async findByEvaluationId(evaluationId: number): Promise<EvaluationFeedback[]> {
|
|
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]);
|
|
}
|
|
}
|