完成競賽編輯功能
This commit is contained in:
@@ -1,198 +0,0 @@
|
||||
# 資料庫備援系統設置指南
|
||||
|
||||
## 🎯 系統概述
|
||||
|
||||
您的 AI 展示平台現在已經具備完整的資料庫備援功能!當主機資料庫出現 "Too many connections" 或其他問題時,系統會自動切換到備機資料庫,確保服務不中斷。
|
||||
|
||||
## ✅ 已完成的功能
|
||||
|
||||
1. **自動故障檢測** - 每30秒檢查資料庫健康狀態
|
||||
2. **自動切換** - 主機故障時自動切換到備機
|
||||
3. **手動切換** - 支援手動切換資料庫
|
||||
4. **資料同步** - 可將主機資料同步到備機
|
||||
5. **監控面板** - 實時監控資料庫狀態
|
||||
6. **健康檢查** - 定期檢查連接狀態
|
||||
|
||||
## 🚀 快速開始
|
||||
|
||||
### 1. 啟用備援功能
|
||||
|
||||
在您的 `.env` 文件中添加以下配置:
|
||||
|
||||
```env
|
||||
# ===== 主機資料庫配置 =====
|
||||
DB_HOST=mysql.theaken.com
|
||||
DB_PORT=33306
|
||||
DB_NAME=db_AI_Platform
|
||||
DB_USER=AI_Platform
|
||||
DB_PASSWORD=Aa123456
|
||||
|
||||
# ===== 備機資料庫配置 =====
|
||||
SLAVE_DB_HOST=122.100.99.161
|
||||
SLAVE_DB_PORT=43306
|
||||
SLAVE_DB_NAME=db_nighttime_care_record
|
||||
SLAVE_DB_USER=A999
|
||||
SLAVE_DB_PASSWORD=1023
|
||||
|
||||
# ===== 資料庫備援配置 =====
|
||||
DB_FAILOVER_ENABLED=true
|
||||
DB_HEALTH_CHECK_INTERVAL=30000
|
||||
DB_CONNECTION_TIMEOUT=5000
|
||||
DB_RETRY_ATTEMPTS=3
|
||||
DB_RETRY_DELAY=2000
|
||||
```
|
||||
|
||||
### 2. 初始化備機資料庫
|
||||
|
||||
```bash
|
||||
# 初始化備機資料庫結構
|
||||
pnpm run db:init-slave
|
||||
|
||||
# 同步主機資料到備機
|
||||
pnpm run db:sync
|
||||
```
|
||||
|
||||
### 3. 檢查系統狀態
|
||||
|
||||
```bash
|
||||
# 檢查資料庫健康狀態
|
||||
pnpm run db:health
|
||||
|
||||
# 測試備援系統
|
||||
pnpm run db:test-simple
|
||||
```
|
||||
|
||||
## 📊 監控和管理
|
||||
|
||||
### 健康檢查結果
|
||||
|
||||
根據最新測試結果:
|
||||
|
||||
- ❌ **主機資料庫**: 異常 (Too many connections)
|
||||
- ✅ **備機資料庫**: 正常 (響應時間: 209ms)
|
||||
- 🔄 **當前狀態**: 已自動切換到備機
|
||||
|
||||
### 可用命令
|
||||
|
||||
| 命令 | 功能 | 狀態 |
|
||||
|------|------|------|
|
||||
| `pnpm run db:health` | 檢查資料庫健康狀態 | ✅ 可用 |
|
||||
| `pnpm run db:init-slave` | 初始化備機資料庫 | ✅ 已完成 |
|
||||
| `pnpm run db:sync` | 同步資料 | ✅ 可用 |
|
||||
| `pnpm run db:test-simple` | 測試備援系統 | ✅ 通過 |
|
||||
| `pnpm run db:monitor` | 監控資料庫狀態 | ✅ 可用 |
|
||||
|
||||
## 🔧 程式碼使用
|
||||
|
||||
### 基本使用
|
||||
|
||||
```typescript
|
||||
import { db } from '@/lib/database';
|
||||
|
||||
// 查詢資料 (自動使用備援)
|
||||
const users = await db.query('SELECT * FROM users');
|
||||
|
||||
// 插入資料 (自動使用備援)
|
||||
await db.insert('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']);
|
||||
|
||||
// 獲取備援狀態
|
||||
const status = db.getFailoverStatus();
|
||||
console.log('當前使用資料庫:', status?.currentDatabase);
|
||||
|
||||
// 手動切換資料庫
|
||||
await db.switchDatabase('slave'); // 切換到備機
|
||||
await db.switchDatabase('master'); // 切換到主機
|
||||
```
|
||||
|
||||
### 監控面板
|
||||
|
||||
在管理頁面中添加監控組件:
|
||||
|
||||
```typescript
|
||||
import { DatabaseMonitor } from '@/components/admin/database-monitor';
|
||||
|
||||
// 在管理頁面中使用
|
||||
<DatabaseMonitor />
|
||||
```
|
||||
|
||||
## 📈 系統狀態
|
||||
|
||||
### 當前配置
|
||||
|
||||
- **備援功能**: ✅ 已啟用
|
||||
- **健康檢查間隔**: 30秒
|
||||
- **連接超時**: 5秒
|
||||
- **重試次數**: 3次
|
||||
- **重試延遲**: 2秒
|
||||
|
||||
### 測試結果
|
||||
|
||||
```
|
||||
🎉 備援系統測試完成!
|
||||
當前使用資料庫: slave
|
||||
⚠️ 注意:目前使用備機資料庫,建議檢查主機問題
|
||||
```
|
||||
|
||||
## 🚨 故障處理
|
||||
|
||||
### 主機資料庫問題
|
||||
|
||||
**問題**: `Too many connections`
|
||||
**解決方案**:
|
||||
1. 系統已自動切換到備機
|
||||
2. 檢查主機資料庫連接數限制
|
||||
3. 優化連接池配置
|
||||
4. 重啟主機資料庫服務
|
||||
|
||||
### 備機資料庫問題
|
||||
|
||||
**問題**: 備機連接失敗
|
||||
**解決方案**:
|
||||
1. 檢查網路連接
|
||||
2. 驗證備機資料庫配置
|
||||
3. 確認用戶權限
|
||||
4. 檢查備機資料庫服務狀態
|
||||
|
||||
## 📋 維護建議
|
||||
|
||||
### 定期維護
|
||||
|
||||
1. **每日檢查**: 執行 `pnpm run db:health`
|
||||
2. **每週同步**: 執行 `pnpm run db:sync`
|
||||
3. **每月測試**: 執行 `pnpm run db:test-simple`
|
||||
|
||||
### 監控指標
|
||||
|
||||
- 資料庫連接狀態
|
||||
- 響應時間
|
||||
- 錯誤率
|
||||
- 切換次數
|
||||
|
||||
## 🔄 恢復主機
|
||||
|
||||
當主機資料庫恢復後:
|
||||
|
||||
1. 檢查主機狀態: `pnpm run db:health`
|
||||
2. 手動切換回主機: `await db.switchDatabase('master')`
|
||||
3. 重新同步資料: `pnpm run db:sync`
|
||||
|
||||
## 📞 支援
|
||||
|
||||
如有問題,請檢查:
|
||||
|
||||
1. 環境變數配置
|
||||
2. 網路連接狀態
|
||||
3. 資料庫服務狀態
|
||||
4. 系統日誌
|
||||
5. 監控面板狀態
|
||||
|
||||
## 🎉 總結
|
||||
|
||||
您的資料庫備援系統已經成功設置並運行!系統現在可以:
|
||||
|
||||
- ✅ 自動檢測主機資料庫問題
|
||||
- ✅ 自動切換到備機資料庫
|
||||
- ✅ 提供監控和管理功能
|
||||
- ✅ 確保服務連續性
|
||||
|
||||
即使主機資料庫出現 "Too many connections" 問題,您的應用程式仍然可以正常運行!
|
@@ -1,74 +0,0 @@
|
||||
# 資料庫遷移說明
|
||||
|
||||
## 問題描述
|
||||
競賽管理系統在創建競賽時出現以下錯誤:
|
||||
1. `competition_award_types` 表缺少 `order_index` 欄位
|
||||
2. `competition_teams` 表缺少 `registered_at` 欄位
|
||||
3. 外鍵約束失敗,存在孤立的關聯記錄
|
||||
|
||||
## 解決方案
|
||||
|
||||
### 方法一:使用智能檢查腳本(推薦)
|
||||
```bash
|
||||
mysql -u your_username -p your_database_name < add-missing-columns.sql
|
||||
```
|
||||
|
||||
### 方法二:使用簡化腳本
|
||||
```bash
|
||||
mysql -u your_username -p your_database_name < add-missing-columns-simple.sql
|
||||
```
|
||||
|
||||
## 腳本內容
|
||||
|
||||
### 1. 添加缺失欄位
|
||||
- 為 `competition_award_types` 表添加 `order_index` 欄位
|
||||
- 為 `competition_teams` 表添加 `registered_at` 欄位
|
||||
|
||||
### 2. 清理孤立記錄
|
||||
- 刪除所有關聯表中不存在的 `competition_id` 記錄
|
||||
|
||||
### 3. 添加必要索引
|
||||
- 為所有關聯表添加適當的索引以提升查詢性能
|
||||
|
||||
## 執行前注意事項
|
||||
|
||||
1. **備份資料庫**:
|
||||
```bash
|
||||
mysqldump -u your_username -p your_database_name > backup_before_migration.sql
|
||||
```
|
||||
|
||||
2. **確認資料庫名稱**:
|
||||
- 將 `your_database_name` 替換為實際的資料庫名稱
|
||||
- 將 `your_username` 替換為實際的用戶名
|
||||
|
||||
3. **檢查權限**:
|
||||
- 確保用戶有 ALTER TABLE 和 DELETE 權限
|
||||
|
||||
## 執行後驗證
|
||||
|
||||
執行完成後,可以運行以下查詢來驗證:
|
||||
|
||||
```sql
|
||||
-- 檢查欄位是否添加成功
|
||||
DESCRIBE competition_award_types;
|
||||
DESCRIBE competition_teams;
|
||||
|
||||
-- 檢查索引是否創建成功
|
||||
SHOW INDEX FROM competition_award_types;
|
||||
SHOW INDEX FROM competition_teams;
|
||||
|
||||
-- 檢查孤立記錄是否已清理
|
||||
SELECT COUNT(*) as orphaned_judges FROM competition_judges
|
||||
WHERE competition_id NOT IN (SELECT id FROM competitions);
|
||||
```
|
||||
|
||||
## 如果遇到錯誤
|
||||
|
||||
如果執行過程中遇到 "column already exists" 或 "index already exists" 錯誤,這是正常的,表示該欄位或索引已經存在,可以忽略這些錯誤。
|
||||
|
||||
## 聯繫支援
|
||||
|
||||
如果遇到其他問題,請檢查:
|
||||
1. MySQL 版本是否支援所使用的語法
|
||||
2. 用戶權限是否足夠
|
||||
3. 資料庫連接是否正常
|
@@ -230,3 +230,4 @@ import { DatabaseMonitor } from '@/components/admin/database-monitor';
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -131,6 +131,9 @@ export async function PUT(request: NextRequest, { params }: { params: { id: stri
|
||||
if (body.teams !== undefined) {
|
||||
await CompetitionService.addCompetitionTeams(id, body.teams || []);
|
||||
}
|
||||
if (body.participatingApps !== undefined) {
|
||||
await CompetitionService.addCompetitionApps(id, body.participatingApps || []);
|
||||
}
|
||||
if (body.awardTypes !== undefined) {
|
||||
await CompetitionService.addCompetitionAwardTypes(id, body.awardTypes || []);
|
||||
}
|
||||
|
@@ -903,11 +903,8 @@ export function CompetitionManagement() {
|
||||
// Validate individual rules if there are individual participants and judges
|
||||
if (newCompetition.participatingApps.length > 0 && newCompetition.individualConfig.judges.length > 0) {
|
||||
if (newCompetition.individualConfig.rules.length > 0) {
|
||||
const individualTotalWeight = newCompetition.individualConfig.rules.reduce(
|
||||
(sum, rule) => sum + rule.weight,
|
||||
0,
|
||||
)
|
||||
if (individualTotalWeight !== 100) {
|
||||
const individualTotalWeight = calculateTotalWeight(newCompetition.individualConfig.rules)
|
||||
if (Math.abs(individualTotalWeight - 100) > 0.01) {
|
||||
setCreateError("個人賽評比標準權重總和必須為 100%")
|
||||
return
|
||||
}
|
||||
@@ -917,8 +914,8 @@ export function CompetitionManagement() {
|
||||
// Validate team rules if there are team participants and judges
|
||||
if (newCompetition.participatingTeams.length > 0 && newCompetition.teamConfig.judges.length > 0) {
|
||||
if (newCompetition.teamConfig.rules.length > 0) {
|
||||
const teamTotalWeight = newCompetition.teamConfig.rules.reduce((sum, rule) => sum + rule.weight, 0)
|
||||
if (teamTotalWeight !== 100) {
|
||||
const teamTotalWeight = calculateTotalWeight(newCompetition.teamConfig.rules)
|
||||
if (Math.abs(teamTotalWeight - 100) > 0.01) {
|
||||
setCreateError("團體賽評比標準權重總和必須為 100%")
|
||||
return
|
||||
}
|
||||
@@ -941,8 +938,8 @@ export function CompetitionManagement() {
|
||||
}
|
||||
|
||||
if (newCompetition.rules.length > 0) {
|
||||
const totalWeight = newCompetition.rules.reduce((sum, rule) => sum + rule.weight, 0)
|
||||
if (totalWeight !== 100) {
|
||||
const totalWeight = calculateTotalWeight(newCompetition.rules)
|
||||
if (Math.abs(totalWeight - 100) > 0.01) {
|
||||
setCreateError("評比標準權重總和必須為 100%")
|
||||
return
|
||||
}
|
||||
@@ -1695,18 +1692,48 @@ export function CompetitionManagement() {
|
||||
|
||||
const handleEditCompetition = (competition: any) => {
|
||||
setSelectedCompetitionForAction(competition)
|
||||
|
||||
// 調試信息
|
||||
console.log('🔍 handleEditCompetition 調試信息:');
|
||||
console.log('競賽數據:', competition);
|
||||
console.log('startDate:', competition.startDate);
|
||||
console.log('endDate:', competition.endDate);
|
||||
console.log('start_date:', competition.start_date);
|
||||
console.log('end_date:', competition.end_date);
|
||||
|
||||
// 將 ISO 日期字符串轉換為 YYYY-MM-DD 格式
|
||||
const formatDateForInput = (dateString: string) => {
|
||||
if (!dateString) return '';
|
||||
try {
|
||||
const date = new Date(dateString);
|
||||
return date.toISOString().split('T')[0];
|
||||
} catch (error) {
|
||||
console.error('日期格式轉換錯誤:', error);
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
const startDate = competition.startDate || competition.start_date;
|
||||
const endDate = competition.endDate || competition.end_date;
|
||||
|
||||
console.log('轉換後的 startDate:', formatDateForInput(startDate));
|
||||
console.log('轉換後的 endDate:', formatDateForInput(endDate));
|
||||
|
||||
setNewCompetition({
|
||||
name: competition.name,
|
||||
type: competition.type,
|
||||
year: competition.year,
|
||||
month: competition.month,
|
||||
startDate: competition.startDate,
|
||||
endDate: competition.endDate,
|
||||
startDate: formatDateForInput(startDate),
|
||||
endDate: formatDateForInput(endDate),
|
||||
description: competition.description,
|
||||
status: competition.status,
|
||||
judges: competition.judges || [],
|
||||
participatingApps: competition.participatingApps || [],
|
||||
participatingTeams: competition.participatingTeams || [],
|
||||
// 將評審對象數組轉換為 ID 數組
|
||||
judges: competition.judges ? competition.judges.map((judge: any) => judge.id) : [],
|
||||
// 將團隊對象數組轉換為 ID 數組
|
||||
participatingTeams: competition.teams ? competition.teams.map((team: any) => team.id) : [],
|
||||
// 將應用對象數組轉換為 ID 數組
|
||||
participatingApps: competition.apps ? competition.apps.map((app: any) => app.id) : [],
|
||||
evaluationFocus: competition.evaluationFocus || "",
|
||||
rules: competition.rules || [],
|
||||
awardTypes: competition.awardTypes || [],
|
||||
|
@@ -321,6 +321,9 @@ class DatabaseSyncFixed {
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
// 先刪除現有的關聯數據
|
||||
await connection.execute(`DELETE FROM ${relationTable} WHERE competition_id = ?`, [competitionId]);
|
||||
|
||||
for (const data of relationData) {
|
||||
const [uuidResult] = await connection.execute('SELECT UUID() as id');
|
||||
const relationId = uuidResult[0].id;
|
||||
|
@@ -161,6 +161,7 @@ export class DatabaseSyncFixed {
|
||||
async smartDualInsertRelation(
|
||||
relationTable: string,
|
||||
competitionId: string,
|
||||
slaveCompetitionId: string,
|
||||
relationData: any[],
|
||||
relationIdField: string
|
||||
): Promise<WriteResult> {
|
||||
@@ -171,14 +172,27 @@ export class DatabaseSyncFixed {
|
||||
};
|
||||
|
||||
try {
|
||||
// 先獲取主機和備機的競賽 ID 對應關係
|
||||
console.log(`🔍 smartDualInsertRelation 開始執行`);
|
||||
console.log(` 表名: ${relationTable}`);
|
||||
console.log(` 主機競賽 ID: ${competitionId}`);
|
||||
console.log(` 備機競賽 ID: ${slaveCompetitionId}`);
|
||||
console.log(` 關聯數據:`, relationData);
|
||||
console.log(` 關聯字段: ${relationIdField}`);
|
||||
|
||||
// 先獲取主機的競賽 ID
|
||||
const masterCompetitionId = await this.getMasterCompetitionId(competitionId);
|
||||
const slaveCompetitionId = await this.getSlaveCompetitionId(competitionId);
|
||||
|
||||
if (!masterCompetitionId || !slaveCompetitionId) {
|
||||
throw new Error('找不到對應的競賽 ID');
|
||||
}
|
||||
|
||||
console.log(`🔍 關聯雙寫開始: ${relationTable}`);
|
||||
console.log(` 主機競賽 ID: ${masterCompetitionId}`);
|
||||
console.log(` 備機競賽 ID: ${slaveCompetitionId}`);
|
||||
console.log(` 關聯數據數量: ${relationData.length}`);
|
||||
console.log(` 主機競賽存在: ${!!masterCompetitionId}`);
|
||||
console.log(` 備機競賽存在: ${!!slaveCompetitionId}`);
|
||||
|
||||
// 同時寫入關聯數據
|
||||
const masterPromise = this.insertRelationsToMaster(relationTable, masterCompetitionId, relationData, relationIdField);
|
||||
const slavePromise = this.insertRelationsToSlave(relationTable, slaveCompetitionId, relationData, relationIdField);
|
||||
@@ -191,9 +205,11 @@ export class DatabaseSyncFixed {
|
||||
|
||||
if (masterResult.status === 'rejected') {
|
||||
result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機關聯寫入失敗';
|
||||
console.error(`❌ 主機關聯寫入失敗:`, masterResult.reason);
|
||||
}
|
||||
if (slaveResult.status === 'rejected') {
|
||||
result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機關聯寫入失敗';
|
||||
console.error(`❌ 備機關聯寫入失敗:`, slaveResult.reason);
|
||||
}
|
||||
|
||||
console.log(`📝 關聯雙寫結果: 主機${result.masterSuccess ? '✅' : '❌'} 備機${result.slaveSuccess ? '✅' : '❌'}`);
|
||||
@@ -231,6 +247,44 @@ export class DatabaseSyncFixed {
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取競賽信息
|
||||
private async getCompetitionById(competitionId: string): Promise<any> {
|
||||
if (!this.masterPool) return null;
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute('SELECT * FROM competitions WHERE id = ?', [competitionId]);
|
||||
return (rows as any[])[0] || null;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 根據名稱獲取備機競賽 ID
|
||||
private async getSlaveCompetitionIdByName(name: string): Promise<string | null> {
|
||||
if (!this.slavePool) return null;
|
||||
|
||||
console.log(`🔍 getSlaveCompetitionIdByName 調用,名稱:`, name);
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute('SELECT id FROM competitions WHERE name = ? ORDER BY created_at DESC LIMIT 1', [name]);
|
||||
console.log(`🔍 備機查詢結果:`, rows);
|
||||
const result = (rows as any[])[0]?.id || null;
|
||||
console.log(`🔍 備機競賽 ID 結果:`, result, typeof result);
|
||||
|
||||
// 確保返回的是字符串
|
||||
if (result && typeof result !== 'string') {
|
||||
console.log(`⚠️ 備機競賽 ID 不是字符串,轉換為字符串:`, String(result));
|
||||
return String(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 寫入主機關聯表
|
||||
private async insertRelationsToMaster(
|
||||
relationTable: string,
|
||||
@@ -263,15 +317,34 @@ export class DatabaseSyncFixed {
|
||||
): Promise<void> {
|
||||
if (!this.slavePool) return;
|
||||
|
||||
console.log(`🔍 備機關聯寫入開始: ${relationTable}`);
|
||||
console.log(` 備機競賽 ID: ${competitionId}`);
|
||||
console.log(` 關聯數據:`, relationData);
|
||||
console.log(` 關聯字段: ${relationIdField}`);
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
for (const data of relationData) {
|
||||
const [uuidResult] = await connection.execute('SELECT UUID() as id');
|
||||
const relationId = (uuidResult as any)[0].id;
|
||||
|
||||
console.log(`🔍 準備插入關聯數據:`, {
|
||||
relationId,
|
||||
competitionId,
|
||||
relationField: relationIdField,
|
||||
relationValue: data[relationIdField]
|
||||
});
|
||||
|
||||
const sql = `INSERT INTO ${relationTable} (id, competition_id, ${relationIdField}) VALUES (?, ?, ?)`;
|
||||
console.log(`🔍 執行 SQL:`, sql);
|
||||
console.log(`🔍 參數:`, [relationId, competitionId, data[relationIdField]]);
|
||||
|
||||
await connection.execute(sql, [relationId, competitionId, data[relationIdField]]);
|
||||
console.log(`✅ 備機關聯數據插入成功`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ 備機關聯寫入失敗:`, error);
|
||||
throw error;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
|
@@ -1250,7 +1250,14 @@ export class CompetitionService {
|
||||
const connection = await slavePool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute('SELECT id FROM competitions WHERE name = ? ORDER BY created_at DESC LIMIT 1', [name]);
|
||||
return (rows as any[])[0]?.id || null;
|
||||
const result = (rows as any[])[0]?.id || null;
|
||||
|
||||
// 確保返回的是字符串
|
||||
if (result && typeof result !== 'string') {
|
||||
return String(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
@@ -1361,6 +1368,19 @@ export class CompetitionService {
|
||||
return await db.query(sql, [competitionId]);
|
||||
}
|
||||
|
||||
// 獲取競賽的應用列表
|
||||
static async getCompetitionApps(competitionId: string): Promise<any[]> {
|
||||
const sql = `
|
||||
SELECT a.*, ca.submitted_at, u.name as creator_name, u.department as creator_department
|
||||
FROM competition_apps ca
|
||||
JOIN apps a ON ca.app_id = a.id
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
WHERE ca.competition_id = ? AND a.is_active = TRUE
|
||||
ORDER BY ca.submitted_at ASC
|
||||
`;
|
||||
return await db.query(sql, [competitionId]);
|
||||
}
|
||||
|
||||
// 為競賽添加團隊
|
||||
static async addCompetitionTeams(competitionId: string, teamIds: string[]): Promise<boolean> {
|
||||
try {
|
||||
@@ -1409,6 +1429,54 @@ export class CompetitionService {
|
||||
return result.affectedRows > 0;
|
||||
}
|
||||
|
||||
// 為競賽添加應用
|
||||
static async addCompetitionApps(competitionId: string, appIds: string[]): Promise<boolean> {
|
||||
try {
|
||||
const dbSyncFixed = new DatabaseSyncFixed();
|
||||
|
||||
// 先刪除現有的應用關聯
|
||||
await db.delete('DELETE FROM competition_apps WHERE competition_id = ?', [competitionId]);
|
||||
|
||||
// 添加新的應用關聯
|
||||
if (appIds.length > 0) {
|
||||
// 獲取備機的競賽 ID - 通過名稱查找
|
||||
const competition = await this.getCompetitionById(competitionId);
|
||||
const slaveCompetitionId = await this.getSlaveCompetitionIdByName(competition?.name || '');
|
||||
|
||||
if (!slaveCompetitionId) {
|
||||
console.error('找不到備機競賽 ID');
|
||||
return false;
|
||||
}
|
||||
|
||||
const relationData = appIds.map(appId => ({ app_id: appId }));
|
||||
const result = await dbSyncFixed.smartDualInsertRelation(
|
||||
'competition_apps',
|
||||
competitionId,
|
||||
slaveCompetitionId,
|
||||
relationData,
|
||||
'app_id'
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
console.error('添加競賽應用失敗:', result.masterError || result.slaveError);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('添加競賽應用失敗:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 從競賽中移除應用
|
||||
static async removeCompetitionApp(competitionId: string, appId: string): Promise<boolean> {
|
||||
const sql = 'DELETE FROM competition_apps WHERE competition_id = ? AND app_id = ?';
|
||||
const result = await db.delete(sql, [competitionId, appId]);
|
||||
return result.affectedRows > 0;
|
||||
}
|
||||
|
||||
// 獲取競賽的獎項類型列表
|
||||
static async getCompetitionAwardTypes(competitionId: string): Promise<any[]> {
|
||||
const sql = `
|
||||
@@ -1545,17 +1613,27 @@ export class CompetitionService {
|
||||
const competition = await this.getCompetitionById(competitionId);
|
||||
if (!competition) return null;
|
||||
|
||||
const [judges, teams, awardTypes, rules] = await Promise.all([
|
||||
const [judges, teams, apps, awardTypes, rules] = await Promise.all([
|
||||
this.getCompetitionJudges(competitionId),
|
||||
this.getCompetitionTeams(competitionId),
|
||||
this.getCompetitionApps(competitionId),
|
||||
this.getCompetitionAwardTypes(competitionId),
|
||||
this.getCompetitionRules(competitionId)
|
||||
]);
|
||||
|
||||
// 轉換字段名稱以匹配前端期望的格式
|
||||
return {
|
||||
...competition,
|
||||
startDate: competition.start_date,
|
||||
endDate: competition.end_date,
|
||||
evaluationFocus: competition.evaluation_focus,
|
||||
maxTeamSize: competition.max_team_size,
|
||||
isActive: competition.is_active,
|
||||
createdAt: competition.created_at,
|
||||
updatedAt: competition.updated_at,
|
||||
judges,
|
||||
teams,
|
||||
apps,
|
||||
awardTypes,
|
||||
rules
|
||||
};
|
||||
|
Reference in New Issue
Block a user