Files
ai-showcase-platform/lib/services/database-service.ts

622 lines
22 KiB
TypeScript

// =====================================================
// 資料庫服務層
// =====================================================
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<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
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<User | null> {
const sql = 'SELECT * FROM users WHERE email = ? AND is_active = TRUE';
return await db.queryOne<User>(sql, [email]);
}
// 根據ID獲取用戶
async findById(id: string): Promise<User | null> {
const sql = 'SELECT * FROM users WHERE id = ? AND is_active = TRUE';
return await db.queryOne<User>(sql, [id]);
}
// 更新用戶
async update(id: string, updates: Partial<User>): Promise<User | null> {
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<boolean> {
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<UserStatistics | null> {
const sql = 'SELECT * FROM user_statistics WHERE id = ?';
return await db.queryOne<UserStatistics>(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<User>(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<T = any>(sql: string, params: any[] = []): Promise<T[]> {
return await db.query<T>(sql, params);
}
// 通用單一查詢方法
async queryOne<T = any>(sql: string, params: any[] = []): Promise<T | null> {
return await db.queryOne<T>(sql, params);
}
// 獲取所有用戶
async getAllUsers(limit = 50, offset = 0): Promise<User[]> {
const sql = 'SELECT * FROM users WHERE is_active = TRUE ORDER BY created_at DESC LIMIT ? OFFSET ?';
return await db.query<User>(sql, [limit, offset]);
}
// 靜態方法保持向後兼容
static async createUser(userData: Omit<User, 'id' | 'created_at' | 'updated_at'>): Promise<User> {
const service = new UserService();
return await service.create(userData);
}
static async getUserByEmail(email: string): Promise<User | null> {
const service = new UserService();
return await service.findByEmail(email);
}
static async getUserById(id: string): Promise<User | null> {
const service = new UserService();
return await service.findById(id);
}
static async updateUser(id: string, updates: Partial<User>): Promise<boolean> {
const service = new UserService();
const result = await service.update(id, updates);
return result !== null;
}
static async getUserStatistics(id: string): Promise<UserStatistics | null> {
const service = new UserService();
return await service.getUserStatistics(id);
}
static async getAllUsers(limit = 50, offset = 0): Promise<User[]> {
const service = new UserService();
return await service.getAllUsers(limit, offset);
}
}
// =====================================================
// 評審服務
// =====================================================
export class JudgeService {
// 創建評審
static async createJudge(judgeData: Omit<Judge, 'id' | 'created_at' | 'updated_at'>): Promise<Judge> {
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<Judge | null> {
const sql = 'SELECT * FROM judges WHERE name = ? AND is_active = TRUE';
const result = await db.queryOne<Judge>(sql, [name]);
if (result) {
result.expertise = JSON.parse(result.expertise as any);
}
return result;
}
// 根據ID獲取評審
static async getJudgeById(id: string): Promise<Judge | null> {
const sql = 'SELECT * FROM judges WHERE id = ? AND is_active = TRUE';
const result = await db.queryOne<Judge>(sql, [id]);
if (result) {
result.expertise = JSON.parse(result.expertise as any);
}
return result;
}
// 獲取所有評審
static async getAllJudges(): Promise<Judge[]> {
const sql = 'SELECT * FROM judges WHERE is_active = TRUE ORDER BY created_at DESC';
const results = await db.query<Judge>(sql);
return results.map(judge => ({
...judge,
expertise: JSON.parse(judge.expertise as any)
}));
}
// 更新評審
static async updateJudge(id: string, updates: Partial<Judge>): Promise<boolean> {
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<Competition, 'id' | 'created_at' | 'updated_at'>): Promise<Competition> {
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<Competition | null> {
const sql = 'SELECT * FROM competitions WHERE name = ? AND is_active = TRUE';
return await db.queryOne<Competition>(sql, [name]);
}
// 根據ID獲取競賽
static async getCompetitionById(id: string): Promise<Competition | null> {
const sql = 'SELECT * FROM competitions WHERE id = ? AND is_active = TRUE';
return await db.queryOne<Competition>(sql, [id]);
}
// 獲取所有競賽
static async getAllCompetitions(): Promise<Competition[]> {
const sql = 'SELECT * FROM competitions WHERE is_active = TRUE ORDER BY year DESC, month DESC';
return await db.query<Competition>(sql);
}
// 獲取競賽統計
static async getCompetitionStatistics(id: string): Promise<CompetitionStatistics | null> {
const sql = 'SELECT * FROM competition_statistics WHERE id = ?';
return await db.queryOne<CompetitionStatistics>(sql, [id]);
}
// 更新競賽
static async updateCompetition(id: string, updates: Partial<Competition>): Promise<boolean> {
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<App, 'id' | 'created_at' | 'updated_at'>): Promise<App> {
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<App | null> {
const sql = 'SELECT * FROM apps WHERE name = ? AND is_active = TRUE';
return await db.queryOne<App>(sql, [name]);
}
// 根據ID獲取應用
static async getAppById(id: string): Promise<App | null> {
const sql = 'SELECT * FROM apps WHERE id = ? AND is_active = TRUE';
return await db.queryOne<App>(sql, [id]);
}
// 獲取所有應用
static async getAllApps(limit = 50, offset = 0): Promise<App[]> {
const sql = 'SELECT * FROM apps WHERE is_active = TRUE ORDER BY created_at DESC LIMIT ? OFFSET ?';
return await db.query<App>(sql, [limit, offset]);
}
// 獲取應用統計
static async getAppStatistics(id: string): Promise<AppStatistics | null> {
const sql = 'SELECT * FROM app_statistics WHERE id = ?';
return await db.queryOne<AppStatistics>(sql, [id]);
}
// 更新應用
static async updateApp(id: string, updates: Partial<App>): Promise<boolean> {
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<AppJudgeScore, 'id' | 'submitted_at'>): Promise<AppJudgeScore> {
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<AppJudgeScore | null> {
const sql = 'SELECT * FROM app_judge_scores WHERE judge_id = ? AND app_id = ?';
return await db.queryOne<AppJudgeScore>(sql, [judgeId, appId]);
}
// 獲取應用的所有評分
static async getAppScores(appId: string): Promise<AppJudgeScore[]> {
const sql = 'SELECT * FROM app_judge_scores WHERE app_id = ? ORDER BY submitted_at DESC';
return await db.query<AppJudgeScore>(sql, [appId]);
}
// 提交提案評分
static async submitProposalScore(scoreData: Omit<ProposalJudgeScore, 'id' | 'submitted_at'>): Promise<ProposalJudgeScore> {
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<ProposalJudgeScore | null> {
const sql = 'SELECT * FROM proposal_judge_scores WHERE judge_id = ? AND proposal_id = ?';
return await db.queryOne<ProposalJudgeScore>(sql, [judgeId, proposalId]);
}
// 獲取提案的所有評分
static async getProposalScores(proposalId: string): Promise<ProposalJudgeScore[]> {
const sql = 'SELECT * FROM proposal_judge_scores WHERE proposal_id = ? ORDER BY submitted_at DESC';
return await db.query<ProposalJudgeScore>(sql, [proposalId]);
}
}
// =====================================================
// 獎項服務
// =====================================================
export class AwardService {
// 創建獎項
static async createAward(awardData: Omit<Award, 'id' | 'created_at'>): Promise<Award> {
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<Award | null> {
const sql = 'SELECT * FROM awards WHERE competition_id = ? AND creator = ? ORDER BY created_at DESC LIMIT 1';
return await db.queryOne<Award>(sql, [competitionId, creator]);
}
// 根據年份獲取獎項
static async getAwardsByYear(year: number): Promise<Award[]> {
const sql = 'SELECT * FROM awards WHERE year = ? ORDER BY month DESC, rank ASC';
return await db.query<Award>(sql, [year]);
}
// 根據競賽獲取獎項
static async getAwardsByCompetition(competitionId: string): Promise<Award[]> {
const sql = 'SELECT * FROM awards WHERE competition_id = ? ORDER BY rank ASC';
return await db.query<Award>(sql, [competitionId]);
}
}
// =====================================================
// 系統設定服務
// =====================================================
export class SystemSettingService {
// 獲取設定值
static async getSetting(key: string): Promise<string | null> {
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<boolean> {
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<SystemSetting[]> {
const sql = 'SELECT * FROM system_settings WHERE is_public = TRUE ORDER BY category, `key`';
return await db.query<SystemSetting>(sql);
}
}