feat: 에러 처리 표준화 + BossRaid 낙관적 잠금
에러 표준화: - pkg/apperror — AppError 타입, 7개 sentinel error - pkg/middleware/error_handler — Fiber ErrorHandler 통합 - 핸들러에서 AppError 반환 시 구조화된 JSON 자동 응답 BossRaid Race Condition: - 상태 전이 4곳 낙관적 잠금 (UPDATE WHERE status=?) - TransitionRoomStatus/TransitionRoomStatusMulti 메서드 추가 - ErrStatusConflict sentinel error Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -237,6 +237,56 @@ func (r *Repository) UpdateRoomStatus(sessionName string, status RoomStatus) err
|
||||
return result.Error
|
||||
}
|
||||
|
||||
// TransitionRoomStatus atomically updates a room's status only if it currently matches expectedStatus.
|
||||
// Returns ErrStatusConflict if the row was not in the expected state (optimistic locking).
|
||||
func (r *Repository) TransitionRoomStatus(sessionName string, expectedStatus RoomStatus, newStatus RoomStatus, extras map[string]interface{}) error {
|
||||
updates := map[string]interface{}{"status": newStatus}
|
||||
for k, v := range extras {
|
||||
updates[k] = v
|
||||
}
|
||||
result := r.db.Model(&BossRoom{}).
|
||||
Where("session_name = ? AND status = ?", sessionName, expectedStatus).
|
||||
Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrStatusConflict
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransitionRoomStatusMulti atomically updates a room's status only if it currently matches one of the expected statuses.
|
||||
// Returns ErrStatusConflict if the row was not in any of the expected states.
|
||||
func (r *Repository) TransitionRoomStatusMulti(sessionName string, expectedStatuses []RoomStatus, newStatus RoomStatus, extras map[string]interface{}) error {
|
||||
updates := map[string]interface{}{"status": newStatus}
|
||||
for k, v := range extras {
|
||||
updates[k] = v
|
||||
}
|
||||
result := r.db.Model(&BossRoom{}).
|
||||
Where("session_name = ? AND status IN ?", sessionName, expectedStatuses).
|
||||
Updates(updates)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return ErrStatusConflict
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransitionSlotStatus atomically updates a room slot's status only if it currently matches expectedStatus.
|
||||
func (r *Repository) TransitionSlotStatus(sessionName string, expectedStatus SlotStatus, newStatus SlotStatus) error {
|
||||
result := r.db.Model(&RoomSlot{}).
|
||||
Where("session_name = ? AND status = ?", sessionName, expectedStatus).
|
||||
Update("status", newStatus)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
// Slot transition failures are non-fatal — log but don't block
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRoomSlotsByServer returns all room slots for a given server.
|
||||
func (r *Repository) GetRoomSlotsByServer(serverID uint) ([]RoomSlot, error) {
|
||||
var slots []RoomSlot
|
||||
|
||||
Reference in New Issue
Block a user