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

1723 lines
54 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// =====================================================
// 資料庫服務層
// =====================================================
import { db } from '../database';
import bcrypt from 'bcryptjs';
import crypto from 'crypto';
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, status, 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.status,
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 status = "active"';
return await db.queryOne<User>(sql, [email]);
}
// 根據ID獲取用戶
async findById(id: string): Promise<User | null> {
const sql = 'SELECT * FROM users WHERE id = ? AND status = "active"';
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: string[] = [];
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('status = ? AND last_login IS NOT NULL AND last_login >= DATE_SUB(NOW(), INTERVAL 30 DAY)');
params.push('active');
} else if (status === 'inactive') {
whereConditions.push('status = ?');
params.push('inactive');
} else if (status === 'invited') {
whereConditions.push('status = ?');
params.push('invited');
}
}
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, status, 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;
invitedUsers: number;
inactiveUsers: number;
newThisMonth: number;
}> {
const sql = `
SELECT
COUNT(*) as total_users,
COUNT(CASE WHEN status = 'active' AND last_login IS NOT NULL AND last_login >= DATE_SUB(CURDATE(), 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 status = 'invited' THEN 1 END) as invited_users,
COUNT(CASE WHEN status = 'inactive' THEN 1 END) as inactive_users,
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_this_month
FROM users
`;
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,
invitedUsers: stats.invited_users || 0,
inactiveUsers: stats.inactive_users || 0,
newThisMonth: stats.new_this_month || 0
};
}
// 獲取用戶活動記錄
async getUserActivities(
userId: string,
page: number = 1,
limit: number = 10,
filters: {
search?: string;
startDate?: string;
endDate?: string;
activityType?: string;
} = {}
): Promise<{ activities: any[]; pagination: any }> {
// 構建篩選條件
let whereConditions = [];
let dateFilter = '';
// 日期篩選
if (filters.startDate && filters.endDate) {
dateFilter = `AND created_at BETWEEN '${filters.startDate} 00:00:00' AND '${filters.endDate} 23:59:59'`;
} else if (filters.startDate) {
dateFilter = `AND created_at >= '${filters.startDate} 00:00:00'`;
} else if (filters.endDate) {
dateFilter = `AND created_at <= '${filters.endDate} 23:59:59'`;
}
// 活動類型篩選
let activityTypeFilter = '';
if (filters.activityType && filters.activityType !== 'all') {
activityTypeFilter = `AND activity_type = '${filters.activityType}'`;
}
// 搜尋篩選
let searchFilter = '';
if (filters.search) {
searchFilter = `AND action_text LIKE '%${filters.search}%'`;
}
// 使用字符串插值避免 UNION ALL 參數化查詢問題
const baseSql = `
SELECT
'login' as activity_type,
'登入系統' as action_text,
'Calendar' as icon,
'blue' as color,
u.last_login as created_at,
NULL as app_name,
NULL as app_id
FROM users u
WHERE u.id = '${userId}' AND u.last_login IS NOT NULL
UNION ALL
SELECT
'favorite' as activity_type,
CONCAT('收藏應用:', a.name) as action_text,
'Heart' as icon,
'red' as color,
uf.created_at,
a.name as app_name,
a.id as app_id
FROM user_favorites uf
JOIN apps a ON uf.app_id = a.id
WHERE uf.user_id = '${userId}'
UNION ALL
SELECT
'like' as activity_type,
CONCAT('按讚應用:', a.name) as action_text,
'ThumbsUp' as icon,
'green' as color,
ul.liked_at as created_at,
a.name as app_name,
a.id as app_id
FROM user_likes ul
JOIN apps a ON ul.app_id = a.id
WHERE ul.user_id = '${userId}'
UNION ALL
SELECT
'view' as activity_type,
CONCAT('查看應用:', a.name) as action_text,
'Eye' as icon,
'purple' as color,
uv.viewed_at as created_at,
a.name as app_name,
a.id as app_id
FROM user_views uv
JOIN apps a ON uv.app_id = a.id
WHERE uv.user_id = '${userId}'
UNION ALL
SELECT
'rating' as activity_type,
CONCAT('評價應用:', a.name, ' (', ur.rating, '分)') as action_text,
'Star' as icon,
'yellow' as color,
ur.rated_at as created_at,
a.name as app_name,
a.id as app_id
FROM user_ratings ur
JOIN apps a ON ur.app_id = a.id
WHERE ur.user_id = '${userId}'
UNION ALL
SELECT
'create' as activity_type,
CONCAT('創建應用:', a.name) as action_text,
'Plus' as icon,
'blue' as color,
a.created_at,
a.name as app_name,
a.id as app_id
FROM apps a
WHERE a.creator_id = '${userId}'
`;
// 先獲取總數
const countSql = `
SELECT COUNT(*) as total FROM (
${baseSql}
) as activities
WHERE 1=1 ${dateFilter} ${activityTypeFilter} ${searchFilter}
`;
const countResult = await this.query(countSql, []);
const total = countResult[0]?.total || 0;
const totalPages = Math.ceil(total / limit);
const offset = (page - 1) * limit;
// 獲取分頁數據
const dataSql = `
SELECT * FROM (
${baseSql}
) as activities
WHERE 1=1 ${dateFilter} ${activityTypeFilter} ${searchFilter}
ORDER BY created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
const activities = await this.query(dataSql, []);
return {
activities,
pagination: {
page,
limit,
total,
totalPages
}
};
}
// 獲取用戶詳細統計數據
async getUserDetailedStats(userId: string): Promise<any> {
// 獲取創建應用數量
const appsSql = `
SELECT COUNT(*) as total_apps
FROM apps
WHERE creator_id = '${userId}' AND is_active = TRUE
`;
const appsResult = await this.query(appsSql, []);
const totalApps = appsResult[0]?.total_apps || 0;
// 獲取撰寫評價數量
const reviewsSql = `
SELECT COUNT(*) as total_reviews
FROM user_ratings
WHERE user_id = '${userId}'
`;
const reviewsResult = await this.query(reviewsSql, []);
const totalReviews = reviewsResult[0]?.total_reviews || 0;
// 獲取獲得讚數(用戶創建的應用獲得的總讚數)
const likesSql = `
SELECT COALESCE(SUM(a.likes_count), 0) as total_likes
FROM apps a
WHERE a.creator_id = '${userId}' AND a.is_active = TRUE
`;
const likesResult = await this.query(likesSql, []);
const totalLikes = likesResult[0]?.total_likes || 0;
// 獲取登入天數(從註冊日期到現在的天數)
const loginDaysSql = `
SELECT DATEDIFF(CURDATE(), join_date) as login_days
FROM users
WHERE id = '${userId}'
`;
const loginDaysResult = await this.query(loginDaysSql, []);
const loginDays = loginDaysResult[0]?.login_days || 0;
return {
totalApps,
totalReviews,
totalLikes,
loginDays
};
}
// 更新用戶資料
async updateUser(userId: string, userData: {
name: string;
department: string;
role: string;
status: string;
}): Promise<{ success: boolean; user?: any; error?: string }> {
try {
const sql = `
UPDATE users
SET name = ?, department = ?, role = ?, status = ?, updated_at = NOW()
WHERE id = ?
`;
await this.query(sql, [
userData.name,
userData.department,
userData.role,
userData.status,
userId
]);
// 獲取更新後的用戶資料
const updatedUserSql = `
SELECT id, name, email, department, role, status, join_date, last_login, total_likes, total_views, created_at, updated_at
FROM users
WHERE id = ?
`;
const updatedUser = await this.queryOne(updatedUserSql, [userId]);
if (!updatedUser) {
return { success: false, error: '用戶不存在' };
}
return { success: true, user: updatedUser };
} catch (error) {
console.error('更新用戶錯誤:', error);
return { success: false, error: '更新用戶時發生錯誤' };
}
}
// 刪除用戶
async deleteUser(userId: string): Promise<{ success: boolean; error?: string }> {
try {
// 檢查用戶是否存在
const checkUserSql = `SELECT id FROM users WHERE id = ?`;
const user = await this.queryOne(checkUserSql, [userId]);
if (!user) {
return { success: false, error: '用戶不存在' };
}
// 刪除用戶(由於外鍵約束,相關的活動記錄也會被自動刪除)
const deleteSql = `DELETE FROM users WHERE id = ?`;
await this.query(deleteSql, [userId]);
return { success: true };
} catch (error) {
console.error('刪除用戶錯誤:', error);
return { success: false, error: '刪除用戶時發生錯誤' };
}
}
// 創建邀請用戶
async createInvitedUser(email: string, role: string): Promise<{ success: boolean; user?: any; invitationLink?: string; error?: string }> {
try {
// 生成邀請 token
const invitationToken = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
// 創建邀請用戶記錄
const userId = crypto.randomUUID()
const sql = `
INSERT INTO users (id, name, email, password_hash, department, role, status, join_date, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, CURDATE(), NOW(), NOW())
`;
// 為邀請用戶創建一個臨時密碼(用戶註冊時會覆蓋)
const tempPassword = 'temp_' + Math.random().toString(36).substring(2, 15)
const tempPasswordHash = await bcrypt.hash(tempPassword, 10)
await this.query(sql, [
userId,
'', // 姓名留空,用戶註冊時填寫
email,
tempPasswordHash,
'', // 部門留空,用戶註冊時填寫
role,
'invited'
]);
// 獲取創建的用戶資料
const userSql = `
SELECT id, name, email, department, role, status, join_date, created_at, updated_at
FROM users
WHERE id = ?
`;
const user = await this.queryOne(userSql, [userId])
if (!user) {
return { success: false, error: '創建邀請用戶失敗' }
}
// 生成邀請連結
const invitationLink = `${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/register?token=${invitationToken}&email=${encodeURIComponent(email)}&role=${role}`
return {
success: true,
user: {
...user,
invitationToken,
invitationLink
},
invitationLink
}
} catch (error) {
console.error('創建邀請用戶錯誤:', error);
return { success: false, error: '創建邀請用戶時發生錯誤' };
}
}
// 查找邀請用戶(狀態為 invited
async findInvitedUserByEmail(email: string): Promise<any | null> {
const sql = 'SELECT * FROM users WHERE email = ? AND status = "invited"';
return await this.queryOne(sql, [email]);
}
// 完成邀請用戶註冊
async completeInvitedUserRegistration(
userId: string,
name: string,
department: string,
passwordHash: string,
role: string
): Promise<{ success: boolean; user?: any; error?: string }> {
try {
const sql = `
UPDATE users
SET name = ?, department = ?, password_hash = ?, role = ?, status = 'active', updated_at = NOW()
WHERE id = ? AND status = 'invited'
`;
await this.query(sql, [name, department, passwordHash, role, userId]);
// 獲取更新後的用戶資料
const updatedUserSql = `
SELECT id, name, email, department, role, status, join_date, last_login, total_likes, total_views, created_at, updated_at
FROM users
WHERE id = ?
`;
const updatedUser = await this.queryOne(updatedUserSql, [userId]);
if (!updatedUser) {
return { success: false, error: '用戶不存在或狀態不正確' };
}
return { success: true, user: updatedUser };
} catch (error) {
console.error('完成邀請用戶註冊錯誤:', error);
return { success: false, error: '完成註冊時發生錯誤' };
}
}
// 通用查詢方法
async query<T = any>(sql: string, params: any[] = []): Promise<T[]> {
return await db.query<T>(sql, params);
}
// 獲取儀表板統計數據
async getDashboardStats(): Promise<{
totalUsers: number;
activeUsers: number;
totalApps: number;
totalCompetitions: number;
totalReviews: number;
totalViews: number;
totalLikes: number;
newAppsThisMonth: number;
activeCompetitions: number;
growthRate: number;
}> {
try {
// 用戶統計
const userStats = await this.getUserStats();
// 應用統計
const appStatsSql = `
SELECT
COUNT(*) as total_apps,
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_apps_this_month,
COALESCE(SUM(views_count), 0) as total_views,
COALESCE(SUM(likes_count), 0) as total_likes
FROM apps
WHERE is_active = TRUE
`;
const appStats = await this.queryOne(appStatsSql);
// 評論統計
const reviewStatsSql = `
SELECT COUNT(*) as total_reviews
FROM user_ratings
`;
const reviewStats = await this.queryOne(reviewStatsSql);
// 競賽統計
const competitionStatsSql = `
SELECT
COUNT(*) as total_competitions,
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_competitions
FROM competitions
`;
const competitionStats = await this.queryOne(competitionStatsSql);
// 計算增長率(與上個月比較)
const lastMonthUsersSql = `
SELECT COUNT(*) as last_month_users
FROM users
WHERE created_at >= DATE_SUB(CURDATE(), INTERVAL 60 DAY)
AND created_at < DATE_SUB(CURDATE(), INTERVAL 30 DAY)
`;
const lastMonthUsers = await this.queryOne(lastMonthUsersSql);
const currentMonthUsers = userStats.newThisMonth;
const growthRate = lastMonthUsers?.last_month_users > 0
? Math.round(((currentMonthUsers - lastMonthUsers.last_month_users) / lastMonthUsers.last_month_users) * 100)
: 0;
return {
totalUsers: userStats.totalUsers,
activeUsers: userStats.activeUsers,
totalApps: appStats?.total_apps || 0,
totalCompetitions: competitionStats?.total_competitions || 0,
totalReviews: reviewStats?.total_reviews || 0,
totalViews: appStats?.total_views || 0,
totalLikes: appStats?.total_likes || 0,
newAppsThisMonth: appStats?.new_apps_this_month || 0,
activeCompetitions: competitionStats?.active_competitions || 0,
growthRate: growthRate
};
} catch (error) {
console.error('獲取儀表板統計數據錯誤:', error);
return {
totalUsers: 0,
activeUsers: 0,
totalApps: 0,
totalCompetitions: 0,
totalReviews: 0,
totalViews: 0,
totalLikes: 0,
newAppsThisMonth: 0,
activeCompetitions: 0,
growthRate: 0
};
}
}
// 獲取最新活動
async getRecentActivities(limit: number = 10): Promise<any[]> {
try {
const sql = `
SELECT
'user_register' as activity_type,
'用戶註冊' as activity_name,
CONCAT(name, ' 註冊了平台') as description,
created_at as activity_time,
'user' as icon_type,
'blue' as color
FROM users
WHERE status = 'active'
ORDER BY created_at DESC
LIMIT ${limit}
`;
const activities = await this.query(sql);
return activities.map(activity => ({
id: `user_${activity.activity_time}`,
type: activity.activity_type,
message: activity.description,
time: new Date(activity.activity_time).toLocaleString('zh-TW'),
icon: 'Users',
color: 'text-blue-600'
}));
} catch (error) {
console.error('獲取最新活動錯誤:', error);
return [];
}
}
// 獲取熱門應用
async getTopApps(limit: number = 5): Promise<any[]> {
try {
const sql = `
SELECT
a.id,
a.name,
a.description,
a.views_count as views,
a.likes_count as likes,
COALESCE(AVG(ur.rating), 0) as rating,
a.category,
a.created_at
FROM apps a
LEFT JOIN user_ratings ur ON a.id = ur.app_id
WHERE a.is_active = TRUE
GROUP BY a.id, a.name, a.description, a.views_count, a.likes_count, a.category, a.created_at
ORDER BY (a.views_count + a.likes_count * 2) DESC
LIMIT ${limit}
`;
const apps = await this.query(sql);
return apps.map(app => ({
id: app.id,
name: app.name,
description: app.description,
views: app.views || 0,
likes: app.likes || 0,
rating: Math.round(app.rating * 10) / 10,
category: app.category,
created_at: app.created_at
}));
} catch (error) {
console.error('獲取熱門應用錯誤:', error);
return [];
}
}
// 通用單一查詢方法
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 {
// 創建應用
async createApp(appData: {
name: string;
description: string;
creator_id: string;
category: string;
type: string;
app_url?: string;
icon?: string;
icon_color?: string;
}): Promise<{ success: boolean; app?: any; error?: string }> {
try {
const appId = crypto.randomUUID();
const sql = `
INSERT INTO apps (id, name, description, creator_id, category, type, app_url, icon, icon_color, likes_count, views_count, rating, is_active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0, 0.00, TRUE, NOW(), NOW())
`;
await this.query(sql, [
appId,
appData.name,
appData.description,
appData.creator_id,
appData.category,
appData.type,
appData.app_url || null,
appData.icon || 'Bot',
appData.icon_color || 'from-blue-500 to-purple-500'
]);
// 獲取創建的應用
const createdApp = await this.getAppById(appId);
return { success: true, app: createdApp };
} catch (error) {
console.error('創建應用錯誤:', error);
return { success: false, error: '創建應用時發生錯誤' };
}
}
// 根據 ID 獲取應用(僅已發布)
async getAppById(appId: string): Promise<any | null> {
const sql = `
SELECT
a.*,
u.name as creator_name,
u.department as creator_department
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
WHERE a.id = ? AND a.is_active = TRUE
`;
return await this.queryOne(sql, [appId]);
}
// 根據 ID 獲取應用(任何狀態)
async getAppByIdAnyStatus(appId: string): Promise<any | null> {
const sql = `
SELECT
a.*,
u.name as creator_name,
u.department as creator_department
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
WHERE a.id = ?
`;
return await this.queryOne(sql, [appId]);
}
// 根據名稱獲取應用
async getAppByName(name: string): Promise<any | null> {
const sql = `
SELECT
a.*,
u.name as creator_name,
u.department as creator_department
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
WHERE a.name = ? AND a.is_active = TRUE
`;
return await this.queryOne(sql, [name]);
}
// 獲取所有應用
async getAllApps(filters: {
search?: string;
category?: string;
type?: string;
status?: string;
page?: number;
limit?: number;
} = {}): Promise<{ apps: any[]; total: number }> {
try {
const { search = '', category = 'all', type = 'all', status = 'all', page = 1, limit = 10 } = filters;
// 構建查詢條件
let whereConditions: string[] = [];
let params: any[] = [];
// 根據狀態篩選
if (status && status !== 'all') {
if (status === 'active') {
whereConditions.push('a.is_active = TRUE');
} else if (status === 'inactive') {
whereConditions.push('a.is_active = FALSE');
}
// 如果 status 是 'all' 或其他值,則不添加狀態篩選
}
if (search) {
whereConditions.push('(a.name LIKE ? OR a.description LIKE ? OR u.name LIKE ?)');
params.push(`%${search}%`, `%${search}%`, `%${search}%`);
}
if (category && category !== 'all') {
whereConditions.push('a.category = ?');
params.push(category);
}
if (type && type !== 'all') {
whereConditions.push('a.type = ?');
params.push(type);
}
const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '';
// 獲取總數
const countSql = `
SELECT COUNT(DISTINCT a.id) as total
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
LEFT JOIN user_ratings ur ON a.id = ur.app_id
${whereClause}
`;
const countResult = await this.queryOne(countSql, params);
const total = countResult?.total || 0;
// 獲取應用列表
const offset = (page - 1) * limit;
const sql = `
SELECT
a.*,
u.name as creator_name,
u.department as creator_department,
u.email as creator_email,
COALESCE(AVG(ur.rating), 0) as rating,
COUNT(ur.id) as reviewCount
FROM apps a
LEFT JOIN users u ON a.creator_id = u.id
LEFT JOIN user_ratings ur ON a.id = ur.app_id
${whereClause}
GROUP BY a.id, u.name, u.department, u.email
ORDER BY a.created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
const apps = await this.query(sql, params);
return { apps, total };
} catch (error) {
console.error('獲取應用列表錯誤:', error);
return { apps: [], total: 0 };
}
}
// 更新應用
async updateApp(appId: string, updates: {
name?: string;
description?: string;
category?: string;
type?: string;
app_url?: string;
icon?: string;
icon_color?: string;
}): Promise<{ success: boolean; app?: any; error?: string }> {
try {
const updateFields = [];
const params = [];
if (updates.name !== undefined) {
updateFields.push('name = ?');
params.push(updates.name);
}
if (updates.description !== undefined) {
updateFields.push('description = ?');
params.push(updates.description);
}
if (updates.category !== undefined) {
updateFields.push('category = ?');
params.push(updates.category);
}
if (updates.type !== undefined) {
updateFields.push('type = ?');
params.push(updates.type);
}
if (updates.app_url !== undefined) {
updateFields.push('app_url = ?');
params.push(updates.app_url);
}
if (updates.icon !== undefined) {
updateFields.push('icon = ?');
params.push(updates.icon);
}
if (updates.icon_color !== undefined) {
updateFields.push('icon_color = ?');
params.push(updates.icon_color);
}
if (updateFields.length === 0) {
return { success: false, error: '沒有要更新的欄位' };
}
updateFields.push('updated_at = NOW()');
params.push(appId);
const sql = `
UPDATE apps
SET ${updateFields.join(', ')}
WHERE id = ? AND is_active = TRUE
`;
await this.query(sql, params);
// 獲取更新後的應用
const updatedApp = await this.getAppById(appId);
return { success: true, app: updatedApp };
} catch (error) {
console.error('更新應用錯誤:', error);
return { success: false, error: '更新應用時發生錯誤' };
}
}
// 刪除應用(硬刪除)
async deleteApp(appId: string): Promise<{ success: boolean; error?: string }> {
try {
// 先檢查應用是否存在(任何狀態)
const app = await this.getAppByIdAnyStatus(appId);
if (!app) {
return { success: false, error: '應用不存在' };
}
// 硬刪除應用(從資料庫中完全移除)
const sql = 'DELETE FROM apps WHERE id = ?';
await this.query(sql, [appId]);
return { success: true };
} catch (error) {
console.error('刪除應用錯誤:', error);
return { success: false, error: '刪除應用時發生錯誤' };
}
}
// 切換應用狀態
async toggleAppStatus(appId: string): Promise<{ success: boolean; app?: any; error?: string }> {
try {
// 先獲取當前狀態(任何狀態)
const currentApp = await this.getAppByIdAnyStatus(appId);
if (!currentApp) {
return { success: false, error: '應用不存在' };
}
const newStatus = currentApp.is_active ? false : true;
const sql = 'UPDATE apps SET is_active = ?, updated_at = NOW() WHERE id = ?';
await this.query(sql, [newStatus, appId]);
// 獲取更新後的應用(任何狀態)
const updatedApp = await this.getAppByIdAnyStatus(appId);
return { success: true, app: updatedApp };
} catch (error) {
console.error('切換應用狀態錯誤:', error);
return { success: false, error: '切換應用狀態時發生錯誤' };
}
}
// 獲取應用統計
async getAppStats(): Promise<{
totalApps: number;
activeApps: number;
inactiveApps: number;
pendingApps: number;
totalViews: number;
totalLikes: number;
newThisMonth: number;
}> {
try {
const sql = `
SELECT
COUNT(*) as total_apps,
COUNT(CASE WHEN is_active = TRUE THEN 1 END) as active_apps,
COUNT(CASE WHEN is_active = FALSE THEN 1 END) as inactive_apps,
COALESCE(SUM(views_count), 0) as total_views,
COALESCE(SUM(likes_count), 0) as total_likes,
COUNT(CASE WHEN created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY) THEN 1 END) as new_this_month
FROM apps
`;
const result = await this.queryOne(sql);
return {
totalApps: result?.total_apps || 0,
activeApps: result?.active_apps || 0,
inactiveApps: result?.inactive_apps || 0,
pendingApps: 0, // 目前沒有 pending 狀態
totalViews: result?.total_views || 0,
totalLikes: result?.total_likes || 0,
newThisMonth: result?.new_this_month || 0
};
} catch (error) {
console.error('獲取應用統計錯誤:', error);
return {
totalApps: 0,
activeApps: 0,
inactiveApps: 0,
pendingApps: 0,
totalViews: 0,
totalLikes: 0,
newThisMonth: 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 getAppRatingStats(appId: string): Promise<{
averageRating: number;
totalRatings: number;
ratingDistribution: { rating: number; count: number }[];
}> {
try {
const sql = `
SELECT
AVG(rating) as average_rating,
COUNT(*) as total_ratings
FROM user_ratings
WHERE app_id = ?
`;
const result = await this.queryOne(sql, [appId]);
const distributionSql = `
SELECT
rating,
COUNT(*) as count
FROM user_ratings
WHERE app_id = ?
GROUP BY rating
ORDER BY rating DESC
`;
const distribution = await this.query(distributionSql, [appId]);
return {
averageRating: result.average_rating ? parseFloat(result.average_rating) : 0,
totalRatings: result.total_ratings || 0,
ratingDistribution: distribution.map((row: any) => ({
rating: row.rating,
count: row.count
}))
};
} catch (error) {
console.error('獲取應用評分統計錯誤:', error);
return {
averageRating: 0,
totalRatings: 0,
ratingDistribution: []
};
}
}
// 獲取應用使用統計
async getAppUsageStats(appId: string): Promise<{
dailyUsers: number;
weeklyUsers: number;
monthlyUsers: number;
totalSessions: number;
topDepartments: { department: string; count: number }[];
trendData: { date: string; users: number }[];
}> {
try {
// 今日使用者
const dailySql = `
SELECT COUNT(DISTINCT user_id) as daily_users
FROM user_views
WHERE app_id = ? AND DATE(viewed_at) = CURDATE()
`;
const dailyResult = await this.queryOne(dailySql, [appId]);
// 本週使用者
const weeklySql = `
SELECT COUNT(DISTINCT user_id) as weekly_users
FROM user_views
WHERE app_id = ? AND viewed_at >= DATE_SUB(CURDATE(), INTERVAL 1 WEEK)
`;
const weeklyResult = await this.queryOne(weeklySql, [appId]);
// 本月使用者
const monthlySql = `
SELECT COUNT(DISTINCT user_id) as monthly_users
FROM user_views
WHERE app_id = ? AND viewed_at >= DATE_SUB(CURDATE(), INTERVAL 1 MONTH)
`;
const monthlyResult = await this.queryOne(monthlySql, [appId]);
// 總使用次數
const totalSql = `
SELECT COUNT(*) as total_sessions
FROM user_views
WHERE app_id = ?
`;
const totalResult = await this.queryOne(totalSql, [appId]);
// 部門使用統計
const deptSql = `
SELECT
u.department,
COUNT(*) as count
FROM user_views uv
JOIN users u ON uv.user_id = u.id
WHERE uv.app_id = ?
GROUP BY u.department
ORDER BY count DESC
LIMIT 5
`;
const deptResult = await this.query(deptSql, [appId]);
// 使用趨勢過去7天
const trendSql = `
SELECT
DATE(viewed_at) as date,
COUNT(DISTINCT user_id) as users
FROM user_views
WHERE app_id = ? AND viewed_at >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)
GROUP BY DATE(viewed_at)
ORDER BY date ASC
`;
const trendResult = await this.query(trendSql, [appId]);
return {
dailyUsers: dailyResult.daily_users || 0,
weeklyUsers: weeklyResult.weekly_users || 0,
monthlyUsers: monthlyResult.monthly_users || 0,
totalSessions: totalResult.total_sessions || 0,
topDepartments: deptResult.map((row: any) => ({
department: row.department,
count: row.count
})),
trendData: trendResult.map((row: any) => ({
date: row.date,
users: row.users
}))
};
} catch (error) {
console.error('獲取應用使用統計錯誤:', error);
return {
dailyUsers: 0,
weeklyUsers: 0,
monthlyUsers: 0,
totalSessions: 0,
topDepartments: [],
trendData: []
};
}
}
// 獲取應用評價列表
async getAppReviews(appId: string, limit: number = 10, offset: number = 0): Promise<any[]> {
try {
const sql = `
SELECT
ur.id,
ur.rating,
ur.comment,
ur.rated_at,
u.name as user_name,
u.department as user_department,
u.avatar as user_avatar
FROM user_ratings ur
JOIN users u ON ur.user_id = u.id
WHERE ur.app_id = ?
ORDER BY ur.rated_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
const reviews = await this.query(sql, [appId]);
return reviews.map((review: any) => ({
id: review.id,
rating: review.rating,
review: review.comment || '用戶評價', // 使用 comment 欄位,如果為空則顯示預設文字
ratedAt: review.rated_at,
userName: review.user_name,
userDepartment: review.user_department,
userAvatar: review.user_avatar
}));
} catch (error) {
console.error('獲取應用評價列表錯誤:', error);
return [];
}
}
// 獲取應用評價總數
async getAppReviewCount(appId: string): Promise<number> {
try {
const sql = `
SELECT COUNT(*) as count
FROM user_ratings
WHERE app_id = ?
`;
const result = await this.queryOne(sql, [appId]);
return result.count || 0;
} catch (error) {
console.error('獲取應用評價總數錯誤:', error);
return 0;
}
}
// 刪除評價
async deleteReview(reviewId: string, appId: string): Promise<{ success: boolean; error?: string }> {
try {
// 檢查評價是否存在且屬於該應用
const checkSql = `
SELECT id FROM user_ratings
WHERE id = ? AND app_id = ?
`;
const review = await this.queryOne(checkSql, [reviewId, appId]);
if (!review) {
return { success: false, error: '評價不存在或無權限刪除' };
}
// 刪除評價
const deleteSql = 'DELETE FROM user_ratings WHERE id = ?';
await this.query(deleteSql, [reviewId]);
return { success: true };
} catch (error) {
console.error('刪除評價錯誤:', error);
return { success: false, error: '刪除評價時發生錯誤' };
}
}
// 獲取應用部門列表
async getAppDepartments(): Promise<{ department: string; count: number }[]> {
try {
const sql = `
SELECT
u.department,
COUNT(DISTINCT a.id) as count
FROM apps a
JOIN users u ON a.creator_id = u.id
WHERE a.is_active = TRUE
GROUP BY u.department
ORDER BY count DESC, u.department ASC
`;
const departments = await this.query(sql);
return departments.map((dept: any) => ({
department: dept.department,
count: parseInt(dept.count)
}));
} catch (error) {
console.error('獲取應用部門列表錯誤:', error);
return [];
}
}
// 獲取應用類型列表
async getAppTypes(): Promise<{ type: string; count: number }[]> {
try {
const sql = `
SELECT
type,
COUNT(*) as count
FROM apps
WHERE is_active = TRUE
GROUP BY type
ORDER BY count DESC, type ASC
`;
const types = await this.query(sql);
return types.map((type: any) => ({
type: type.type,
count: parseInt(type.count)
}));
} catch (error) {
console.error('獲取應用類型列表錯誤:', error);
return [];
}
}
// 獲取應用分類列表
async getAppCategories(): Promise<{ category: string; count: number }[]> {
try {
const sql = `
SELECT
category,
COUNT(*) as count
FROM apps
WHERE is_active = TRUE
GROUP BY category
ORDER BY count DESC, category ASC
`;
const categories = await this.query(sql);
return categories.map((cat: any) => ({
category: cat.category,
count: parseInt(cat.count)
}));
} catch (error) {
console.error('獲取應用分類列表錯誤:', error);
return [];
}
}
}
// =====================================================
// 評分服務
// =====================================================
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);
}
}