622 lines
22 KiB
TypeScript
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);
|
|
}
|
|
}
|