新增競賽前台呈現、刪除競賽、修改競賽狀態
This commit is contained in:
@@ -323,7 +323,9 @@ class DatabaseFailover {
|
||||
async query(sql, params) {
|
||||
const connection = await this.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute(sql, params);
|
||||
// 將 undefined 值轉換為 null,避免 MySQL 驅動錯誤
|
||||
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
|
||||
const [rows] = await connection.execute(sql, sanitizedParams);
|
||||
return rows;
|
||||
}
|
||||
finally {
|
||||
@@ -345,7 +347,9 @@ class DatabaseFailover {
|
||||
async insert(sql, params) {
|
||||
const connection = await this.getConnection();
|
||||
try {
|
||||
const [result] = await connection.execute(sql, params);
|
||||
// 將 undefined 值轉換為 null,避免 MySQL 驅動錯誤
|
||||
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
|
||||
const [result] = await connection.execute(sql, sanitizedParams);
|
||||
return result;
|
||||
}
|
||||
finally {
|
||||
@@ -356,7 +360,9 @@ class DatabaseFailover {
|
||||
async update(sql, params) {
|
||||
const connection = await this.getConnection();
|
||||
try {
|
||||
const [result] = await connection.execute(sql, params);
|
||||
// 將 undefined 值轉換為 null,避免 MySQL 驅動錯誤
|
||||
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
|
||||
const [result] = await connection.execute(sql, sanitizedParams);
|
||||
return result;
|
||||
}
|
||||
finally {
|
||||
@@ -367,7 +373,9 @@ class DatabaseFailover {
|
||||
async delete(sql, params) {
|
||||
const connection = await this.getConnection();
|
||||
try {
|
||||
const [result] = await connection.execute(sql, params);
|
||||
// 將 undefined 值轉換為 null,避免 MySQL 驅動錯誤
|
||||
const sanitizedParams = params ? params.map(param => param === undefined ? null : param) : [];
|
||||
const [result] = await connection.execute(sql, sanitizedParams);
|
||||
return result;
|
||||
}
|
||||
finally {
|
||||
|
@@ -137,6 +137,53 @@ class DatabaseSyncFixed {
|
||||
}
|
||||
}
|
||||
|
||||
// 智能雙寫更新
|
||||
async smartDualUpdate(tableName, id, updates) {
|
||||
const result = {
|
||||
success: false,
|
||||
masterSuccess: false,
|
||||
slaveSuccess: false
|
||||
};
|
||||
|
||||
try {
|
||||
// 先獲取主機的記錄
|
||||
const masterRecord = await this.getMasterRecord(tableName, id);
|
||||
|
||||
if (!masterRecord) {
|
||||
throw new Error('主機記錄不存在');
|
||||
}
|
||||
|
||||
// 獲取備機的記錄 ID(通過名稱匹配)
|
||||
const slaveId = await this.getSlaveRecordIdByName(tableName, masterRecord.name);
|
||||
|
||||
if (!slaveId) {
|
||||
throw new Error('備機記錄不存在');
|
||||
}
|
||||
|
||||
// 同時更新主機和備機
|
||||
const masterPromise = this.updateMasterRecord(tableName, id, updates);
|
||||
const slavePromise = this.updateSlaveRecord(tableName, slaveId, updates);
|
||||
|
||||
const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]);
|
||||
|
||||
result.masterSuccess = masterResult.status === 'fulfilled';
|
||||
result.slaveSuccess = slaveResult.status === 'fulfilled';
|
||||
result.success = result.masterSuccess || result.slaveSuccess;
|
||||
|
||||
if (masterResult.status === 'rejected') {
|
||||
result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機更新失敗';
|
||||
}
|
||||
if (slaveResult.status === 'rejected') {
|
||||
result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機更新失敗';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
result.masterError = error instanceof Error ? error.message : '雙寫更新執行失敗';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 智能雙寫關聯表 - 使用對應的競賽 ID
|
||||
async smartDualInsertRelation(relationTable, masterCompetitionId, slaveCompetitionId, relationData, relationIdField) {
|
||||
const result = {
|
||||
@@ -370,6 +417,200 @@ class DatabaseSyncFixed {
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取主機記錄
|
||||
async getMasterRecord(tableName, id) {
|
||||
if (!this.masterPool) return null;
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute(`SELECT * FROM ${tableName} WHERE id = ?`, [id]);
|
||||
return rows[0] || null;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 根據名稱獲取備機記錄 ID
|
||||
async getSlaveRecordIdByName(tableName, name) {
|
||||
if (!this.slavePool) return null;
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute(`SELECT id FROM ${tableName} WHERE name = ? ORDER BY created_at DESC LIMIT 1`, [name]);
|
||||
const result = rows[0]?.id || null;
|
||||
return result && typeof result !== 'string' ? String(result) : result;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新主機記錄
|
||||
async updateMasterRecord(tableName, id, updates) {
|
||||
if (!this.masterPool) return;
|
||||
|
||||
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
|
||||
if (fields.length === 0) return;
|
||||
|
||||
const setClause = fields.map(field => `${field} = ?`).join(', ');
|
||||
const values = fields.map(field => updates[field]);
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
const sql = `UPDATE ${tableName} SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||
await connection.execute(sql, [...values, id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新備機記錄
|
||||
async updateSlaveRecord(tableName, id, updates) {
|
||||
if (!this.slavePool) return;
|
||||
|
||||
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
|
||||
if (fields.length === 0) return;
|
||||
|
||||
const setClause = fields.map(field => `${field} = ?`).join(', ');
|
||||
const values = fields.map(field => updates[field]);
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
const sql = `UPDATE ${tableName} SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||
await connection.execute(sql, [...values, id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有競賽的當前狀態
|
||||
async clearAllCurrentCompetitions() {
|
||||
const result = {
|
||||
success: false,
|
||||
masterSuccess: false,
|
||||
slaveSuccess: false
|
||||
};
|
||||
|
||||
try {
|
||||
// 同時清除主機和備機的所有當前競賽狀態
|
||||
const masterPromise = this.clearMasterCurrentCompetitions();
|
||||
const slavePromise = this.clearSlaveCurrentCompetitions();
|
||||
|
||||
const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]);
|
||||
|
||||
result.masterSuccess = masterResult.status === 'fulfilled';
|
||||
result.slaveSuccess = slaveResult.status === 'fulfilled';
|
||||
result.success = result.masterSuccess || result.slaveSuccess;
|
||||
|
||||
if (masterResult.status === 'rejected') {
|
||||
result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機清除失敗';
|
||||
}
|
||||
if (slaveResult.status === 'rejected') {
|
||||
result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機清除失敗';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
result.masterError = error instanceof Error ? error.message : '清除所有當前競賽執行失敗';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 清除主機的所有當前競賽狀態
|
||||
async clearMasterCurrentCompetitions() {
|
||||
if (!this.masterPool) return;
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
await connection.execute('UPDATE competitions SET is_current = FALSE');
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 清除備機的所有當前競賽狀態
|
||||
async clearSlaveCurrentCompetitions() {
|
||||
if (!this.slavePool) return;
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
await connection.execute('UPDATE competitions SET is_current = FALSE');
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 智能雙寫刪除
|
||||
async smartDualDelete(tableName, id, idField = 'id') {
|
||||
const result = {
|
||||
success: false,
|
||||
masterSuccess: false,
|
||||
slaveSuccess: false
|
||||
};
|
||||
|
||||
try {
|
||||
// 獲取備機對應的 ID
|
||||
let slaveId = null;
|
||||
if (tableName === 'competitions') {
|
||||
// 對於競賽表,先獲取競賽名稱
|
||||
const masterRecord = await this.getMasterRecord(tableName, id);
|
||||
if (masterRecord) {
|
||||
slaveId = await this.getSlaveRecordIdByName(tableName, masterRecord.name);
|
||||
}
|
||||
} else {
|
||||
// 對於關聯表,直接使用主機 ID
|
||||
slaveId = id;
|
||||
}
|
||||
|
||||
// 同時刪除主機和備機的記錄
|
||||
const masterPromise = this.deleteFromMaster(tableName, id, idField);
|
||||
const slavePromise = slaveId ? this.deleteFromSlave(tableName, slaveId, idField) : Promise.resolve();
|
||||
|
||||
const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]);
|
||||
|
||||
result.masterSuccess = masterResult.status === 'fulfilled';
|
||||
result.slaveSuccess = slaveResult.status === 'fulfilled';
|
||||
result.success = result.masterSuccess || result.slaveSuccess;
|
||||
|
||||
if (masterResult.status === 'rejected') {
|
||||
result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機刪除失敗';
|
||||
}
|
||||
if (slaveResult.status === 'rejected') {
|
||||
result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機刪除失敗';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
result.masterError = error instanceof Error ? error.message : '雙寫刪除執行失敗';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 從主機刪除記錄
|
||||
async deleteFromMaster(tableName, id, idField = 'id') {
|
||||
if (!this.masterPool) return;
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
const sql = `DELETE FROM ${tableName} WHERE ${idField} = ?`;
|
||||
await connection.execute(sql, [id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 從備機刪除記錄
|
||||
async deleteFromSlave(tableName, id, idField = 'id') {
|
||||
if (!this.slavePool) return;
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
const sql = `DELETE FROM ${tableName} WHERE ${idField} = ?`;
|
||||
await connection.execute(sql, [id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 清理資源
|
||||
async close() {
|
||||
if (this.masterPool) {
|
||||
|
@@ -157,6 +157,53 @@ export class DatabaseSyncFixed {
|
||||
}
|
||||
}
|
||||
|
||||
// 智能雙寫更新
|
||||
async smartDualUpdate(tableName: string, id: string, updates: Record<string, any>): Promise<WriteResult> {
|
||||
const result: WriteResult = {
|
||||
success: false,
|
||||
masterSuccess: false,
|
||||
slaveSuccess: false
|
||||
};
|
||||
|
||||
try {
|
||||
// 先獲取主機的記錄
|
||||
const masterRecord = await this.getMasterRecord(tableName, id);
|
||||
|
||||
if (!masterRecord) {
|
||||
throw new Error('主機記錄不存在');
|
||||
}
|
||||
|
||||
// 獲取備機的記錄 ID(通過名稱匹配)
|
||||
const slaveId = await this.getSlaveRecordIdByName(tableName, masterRecord.name);
|
||||
|
||||
if (!slaveId) {
|
||||
throw new Error('備機記錄不存在');
|
||||
}
|
||||
|
||||
// 同時更新主機和備機
|
||||
const masterPromise = this.updateMasterRecord(tableName, id, updates);
|
||||
const slavePromise = this.updateSlaveRecord(tableName, slaveId, updates);
|
||||
|
||||
const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]);
|
||||
|
||||
result.masterSuccess = masterResult.status === 'fulfilled';
|
||||
result.slaveSuccess = slaveResult.status === 'fulfilled';
|
||||
result.success = result.masterSuccess || result.slaveSuccess;
|
||||
|
||||
if (masterResult.status === 'rejected') {
|
||||
result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機更新失敗';
|
||||
}
|
||||
if (slaveResult.status === 'rejected') {
|
||||
result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機更新失敗';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
result.masterError = error instanceof Error ? error.message : '雙寫更新執行失敗';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 智能雙寫關聯表 - 使用對應的競賽 ID
|
||||
async smartDualInsertRelation(
|
||||
relationTable: string,
|
||||
@@ -260,6 +307,71 @@ export class DatabaseSyncFixed {
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取主機記錄
|
||||
private async getMasterRecord(tableName: string, id: string): Promise<any> {
|
||||
if (!this.masterPool) return null;
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute(`SELECT * FROM ${tableName} WHERE id = ?`, [id]);
|
||||
return (rows as any[])[0] || null;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 根據名稱獲取備機記錄 ID
|
||||
private async getSlaveRecordIdByName(tableName: string, name: string): Promise<string | null> {
|
||||
if (!this.slavePool) return null;
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
const [rows] = await connection.execute(`SELECT id FROM ${tableName} WHERE name = ? ORDER BY created_at DESC LIMIT 1`, [name]);
|
||||
const result = (rows as any[])[0]?.id || null;
|
||||
return result && typeof result !== 'string' ? String(result) : result;
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新主機記錄
|
||||
private async updateMasterRecord(tableName: string, id: string, updates: Record<string, any>): Promise<void> {
|
||||
if (!this.masterPool) return;
|
||||
|
||||
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
|
||||
if (fields.length === 0) return;
|
||||
|
||||
const setClause = fields.map(field => `${field} = ?`).join(', ');
|
||||
const values = fields.map(field => updates[field]);
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
const sql = `UPDATE ${tableName} SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||
await connection.execute(sql, [...values, id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 更新備機記錄
|
||||
private async updateSlaveRecord(tableName: string, id: string, updates: Record<string, any>): Promise<void> {
|
||||
if (!this.slavePool) return;
|
||||
|
||||
const fields = Object.keys(updates).filter(key => key !== 'id' && key !== 'created_at');
|
||||
if (fields.length === 0) return;
|
||||
|
||||
const setClause = fields.map(field => `${field} = ?`).join(', ');
|
||||
const values = fields.map(field => updates[field]);
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
const sql = `UPDATE ${tableName} SET ${setClause}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`;
|
||||
await connection.execute(sql, [...values, id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 根據名稱獲取備機競賽 ID
|
||||
private async getSlaveCompetitionIdByName(name: string): Promise<string | null> {
|
||||
if (!this.slavePool) return null;
|
||||
@@ -350,6 +462,135 @@ export class DatabaseSyncFixed {
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有競賽的當前狀態
|
||||
async clearAllCurrentCompetitions(): Promise<WriteResult> {
|
||||
const result: WriteResult = {
|
||||
success: false,
|
||||
masterSuccess: false,
|
||||
slaveSuccess: false
|
||||
};
|
||||
|
||||
try {
|
||||
// 同時清除主機和備機的所有當前競賽狀態
|
||||
const masterPromise = this.clearMasterCurrentCompetitions();
|
||||
const slavePromise = this.clearSlaveCurrentCompetitions();
|
||||
|
||||
const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]);
|
||||
|
||||
result.masterSuccess = masterResult.status === 'fulfilled';
|
||||
result.slaveSuccess = slaveResult.status === 'fulfilled';
|
||||
result.success = result.masterSuccess || result.slaveSuccess;
|
||||
|
||||
if (masterResult.status === 'rejected') {
|
||||
result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機清除失敗';
|
||||
}
|
||||
if (slaveResult.status === 'rejected') {
|
||||
result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機清除失敗';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
result.masterError = error instanceof Error ? error.message : '清除所有當前競賽執行失敗';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 清除主機的所有當前競賽狀態
|
||||
private async clearMasterCurrentCompetitions(): Promise<void> {
|
||||
if (!this.masterPool) return;
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
await connection.execute('UPDATE competitions SET is_current = FALSE');
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 清除備機的所有當前競賽狀態
|
||||
private async clearSlaveCurrentCompetitions(): Promise<void> {
|
||||
if (!this.slavePool) return;
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
await connection.execute('UPDATE competitions SET is_current = FALSE');
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 智能雙寫刪除
|
||||
async smartDualDelete(tableName: string, id: string, idField: string = 'id'): Promise<WriteResult> {
|
||||
const result: WriteResult = {
|
||||
success: false,
|
||||
masterSuccess: false,
|
||||
slaveSuccess: false
|
||||
};
|
||||
|
||||
try {
|
||||
// 獲取備機對應的 ID
|
||||
let slaveId: string | null = null;
|
||||
if (tableName === 'competitions') {
|
||||
// 對於競賽表,先獲取競賽名稱
|
||||
const masterRecord = await this.getMasterRecord(tableName, id);
|
||||
if (masterRecord) {
|
||||
slaveId = await this.getSlaveRecordIdByName(tableName, masterRecord.name);
|
||||
}
|
||||
} else {
|
||||
// 對於關聯表,直接使用主機 ID
|
||||
slaveId = id;
|
||||
}
|
||||
|
||||
// 同時刪除主機和備機的記錄
|
||||
const masterPromise = this.deleteFromMaster(tableName, id, idField);
|
||||
const slavePromise = slaveId ? this.deleteFromSlave(tableName, slaveId, idField) : Promise.resolve();
|
||||
|
||||
const [masterResult, slaveResult] = await Promise.allSettled([masterPromise, slavePromise]);
|
||||
|
||||
result.masterSuccess = masterResult.status === 'fulfilled';
|
||||
result.slaveSuccess = slaveResult.status === 'fulfilled';
|
||||
result.success = result.masterSuccess || result.slaveSuccess;
|
||||
|
||||
if (masterResult.status === 'rejected') {
|
||||
result.masterError = masterResult.reason instanceof Error ? masterResult.reason.message : '主機刪除失敗';
|
||||
}
|
||||
if (slaveResult.status === 'rejected') {
|
||||
result.slaveError = slaveResult.reason instanceof Error ? slaveResult.reason.message : '備機刪除失敗';
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
result.masterError = error instanceof Error ? error.message : '雙寫刪除執行失敗';
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 從主機刪除記錄
|
||||
private async deleteFromMaster(tableName: string, id: string, idField: string = 'id'): Promise<void> {
|
||||
if (!this.masterPool) return;
|
||||
|
||||
const connection = await this.masterPool.getConnection();
|
||||
try {
|
||||
const sql = `DELETE FROM ${tableName} WHERE ${idField} = ?`;
|
||||
await connection.execute(sql, [id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 從備機刪除記錄
|
||||
private async deleteFromSlave(tableName: string, id: string, idField: string = 'id'): Promise<void> {
|
||||
if (!this.slavePool) return;
|
||||
|
||||
const connection = await this.slavePool.getConnection();
|
||||
try {
|
||||
const sql = `DELETE FROM ${tableName} WHERE ${idField} = ?`;
|
||||
await connection.execute(sql, [id]);
|
||||
} finally {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
|
||||
// 清理資源
|
||||
async close(): Promise<void> {
|
||||
if (this.masterPool) {
|
||||
|
@@ -616,7 +616,7 @@ export class UserService {
|
||||
const competitionStatsSql = `
|
||||
SELECT
|
||||
COUNT(*) as total_competitions,
|
||||
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_competitions
|
||||
COUNT(CASE WHEN status = 'active' OR status = 'ongoing' THEN 1 END) as active_competitions
|
||||
FROM competitions
|
||||
`;
|
||||
const competitionStats = await this.queryOne(competitionStatsSql);
|
||||
@@ -1282,13 +1282,135 @@ export class CompetitionService {
|
||||
|
||||
// 更新競賽
|
||||
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;
|
||||
try {
|
||||
const dbSyncFixed = new DatabaseSyncFixed();
|
||||
|
||||
// 使用雙寫功能更新競賽
|
||||
const result = await dbSyncFixed.smartDualUpdate('competitions', id, updates);
|
||||
|
||||
if (!result.success) {
|
||||
console.error('競賽更新失敗:', result.masterError || result.slaveError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('競賽更新失敗:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 獲取當前競賽
|
||||
static async getCurrentCompetition(): Promise<any | null> {
|
||||
try {
|
||||
const sql = 'SELECT * FROM competitions WHERE is_current = TRUE AND is_active = TRUE LIMIT 1';
|
||||
const competitions = await db.query<any>(sql);
|
||||
|
||||
if (competitions.length > 0) {
|
||||
const competition = competitions[0];
|
||||
return await this.getCompetitionWithDetails(competition.id);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('獲取當前競賽失敗:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 設置當前競賽
|
||||
static async setCurrentCompetition(competitionId: string): Promise<boolean> {
|
||||
try {
|
||||
const dbSyncFixed = new DatabaseSyncFixed();
|
||||
|
||||
// 先清除所有競賽的當前狀態 - 使用直接 SQL 更新
|
||||
try {
|
||||
await dbSyncFixed.clearAllCurrentCompetitions();
|
||||
} catch (error) {
|
||||
console.error('清除當前競賽狀態失敗:', error);
|
||||
}
|
||||
|
||||
// 設置指定競賽為當前競賽
|
||||
const setResult = await dbSyncFixed.smartDualUpdate('competitions', competitionId, { is_current: true });
|
||||
|
||||
if (!setResult.success) {
|
||||
console.error('設置當前競賽失敗:', setResult.masterError || setResult.slaveError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('設置當前競賽失敗:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 清除當前競賽
|
||||
static async clearCurrentCompetition(): Promise<boolean> {
|
||||
try {
|
||||
await db.update('UPDATE competitions SET is_current = FALSE');
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('清除當前競賽失敗:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 刪除競賽 - 支援雙寫
|
||||
static async deleteCompetition(id: string): Promise<boolean> {
|
||||
try {
|
||||
const dbSyncFixed = new DatabaseSyncFixed();
|
||||
|
||||
// 先獲取競賽信息
|
||||
const competition = await this.getCompetitionById(id);
|
||||
if (!competition) {
|
||||
console.error('競賽不存在');
|
||||
return false;
|
||||
}
|
||||
|
||||
// 刪除關聯數據
|
||||
await this.deleteCompetitionRelations(id);
|
||||
|
||||
// 使用雙寫功能刪除競賽
|
||||
const result = await dbSyncFixed.smartDualDelete('competitions', id);
|
||||
|
||||
if (!result.success) {
|
||||
console.error('競賽刪除失敗:', result.masterError || result.slaveError);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('競賽刪除失敗:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 刪除競賽關聯數據
|
||||
private static async deleteCompetitionRelations(competitionId: string): Promise<void> {
|
||||
try {
|
||||
const dbSyncFixed = new DatabaseSyncFixed();
|
||||
|
||||
// 刪除所有關聯表數據
|
||||
const relations = [
|
||||
'competition_judges',
|
||||
'competition_teams',
|
||||
'competition_apps',
|
||||
'competition_award_types',
|
||||
'competition_rules'
|
||||
];
|
||||
|
||||
for (const relationTable of relations) {
|
||||
try {
|
||||
await dbSyncFixed.smartDualDelete(relationTable, competitionId, 'competition_id');
|
||||
} catch (error) {
|
||||
console.error(`刪除關聯表 ${relationTable} 失敗:`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刪除競賽關聯數據失敗:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
@@ -1369,16 +1491,50 @@ export class CompetitionService {
|
||||
}
|
||||
|
||||
// 獲取競賽的應用列表
|
||||
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 getCompetitionApps(competitionId: string, competitionType?: string): Promise<any[]> {
|
||||
// 先獲取競賽信息
|
||||
const competition = await this.getCompetitionById(competitionId);
|
||||
if (!competition) return [];
|
||||
|
||||
let apps: any[] = [];
|
||||
|
||||
if (competition.type === 'team') {
|
||||
// 對於團隊競賽,獲取所有參賽團隊的應用
|
||||
const teams = await this.getCompetitionTeams(competitionId);
|
||||
if (teams.length > 0) {
|
||||
// 過濾掉 undefined 或 null 的 team_id 值
|
||||
const teamIds = teams
|
||||
.map(t => t.team_id)
|
||||
.filter(id => id !== undefined && id !== null);
|
||||
|
||||
if (teamIds.length > 0) {
|
||||
const placeholders = teamIds.map(() => '?').join(',');
|
||||
|
||||
const sql = `
|
||||
SELECT a.*, u.name as creator_name, u.department as creator_department, t.name as team_name
|
||||
FROM apps a
|
||||
LEFT JOIN users u ON a.creator_id = u.id
|
||||
LEFT JOIN teams t ON a.team_id = t.id
|
||||
WHERE a.team_id IN (${placeholders}) AND a.is_active = TRUE
|
||||
ORDER BY a.created_at ASC
|
||||
`;
|
||||
apps = await db.query(sql, teamIds);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 對於個人競賽,從 competition_apps 表獲取
|
||||
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
|
||||
`;
|
||||
apps = await db.query(sql, [competitionId]);
|
||||
}
|
||||
|
||||
return apps;
|
||||
}
|
||||
|
||||
// 為競賽添加團隊
|
||||
@@ -1621,9 +1777,42 @@ export class CompetitionService {
|
||||
this.getCompetitionRules(competitionId)
|
||||
]);
|
||||
|
||||
// 根據日期動態計算競賽狀態
|
||||
const now = new Date();
|
||||
const startDate = new Date(competition.start_date);
|
||||
const endDate = new Date(competition.end_date);
|
||||
|
||||
let calculatedStatus = competition.status;
|
||||
|
||||
// 確保日期比較的準確性,使用 UTC 時間避免時區問題
|
||||
const nowUTC = new Date(now.getTime() + now.getTimezoneOffset() * 60000);
|
||||
const startDateUTC = new Date(startDate.getTime() + startDate.getTimezoneOffset() * 60000);
|
||||
const endDateUTC = new Date(endDate.getTime() + endDate.getTimezoneOffset() * 60000);
|
||||
|
||||
console.log('🔍 競賽狀態計算:', {
|
||||
competitionId,
|
||||
name: competition.name,
|
||||
now: nowUTC.toISOString(),
|
||||
startDate: startDateUTC.toISOString(),
|
||||
endDate: endDateUTC.toISOString(),
|
||||
originalStatus: competition.status
|
||||
});
|
||||
|
||||
// 根據實際日期計算狀態
|
||||
if (nowUTC < startDateUTC) {
|
||||
calculatedStatus = 'upcoming'; // 即將開始
|
||||
} else if (nowUTC >= startDateUTC && nowUTC <= endDateUTC) {
|
||||
calculatedStatus = 'active'; // 進行中
|
||||
} else if (nowUTC > endDateUTC) {
|
||||
calculatedStatus = 'completed'; // 已完成
|
||||
}
|
||||
|
||||
console.log('🔍 計算後的狀態:', calculatedStatus);
|
||||
|
||||
// 轉換字段名稱以匹配前端期望的格式
|
||||
return {
|
||||
...competition,
|
||||
status: calculatedStatus, // 使用計算後的狀態
|
||||
startDate: competition.start_date,
|
||||
endDate: competition.end_date,
|
||||
evaluationFocus: competition.evaluation_focus,
|
||||
@@ -2625,7 +2814,7 @@ export class AppService {
|
||||
const competitionStatsSql = `
|
||||
SELECT
|
||||
COUNT(*) as total_competitions,
|
||||
COUNT(CASE WHEN status = 'active' THEN 1 END) as active_competitions
|
||||
COUNT(CASE WHEN status = 'active' OR status = 'ongoing' THEN 1 END) as active_competitions
|
||||
FROM competitions
|
||||
`;
|
||||
const competitionStats = await this.queryOne(competitionStatsSql);
|
||||
|
Reference in New Issue
Block a user