atomic-booking-status-transition-guard.md 1.8 KB

Переходы статуса брони должны быть условными (WHERE status=...)

Контекст: BookingHandler.Cancel уже использовал UpdateStatusIfPending (атомарный UPDATE ... WHERE status='pending'), а Confirm вызывал безусловный UpdateStatus(id, "confirmed"). Это позволяло:

  • подтвердить уже отменённую бронь (cancelledconfirmed);
  • получить 200 при подтверждении несуществующего id (0 строк, но без ошибки).

Суть: Изменение статуса — это переход в конечном автомате, а не «затирание поля». Допустимый переход нужно зашивать в WHERE, а число затронутых строк использовать как признак валидности перехода. Иначе появляются недопустимые состояния и race conditions.

Решение: переиспользовать тот же условный апдейт и для подтверждения:

ok, err := h.bookingRepo.UpdateStatusIfPending(r.Context(), id, "confirmed")
if err != nil { /* 500 */ }
if !ok {
    writeError(w, http.StatusConflict, "booking cannot be confirmed in its current state", nil)
    return
}

Безусловный BookingRepo.UpdateStatus удалён как мёртвый и опасный код.

Связанные заметки: [[atomic-refresh-token-race-condition]], [[backend-validation]] Источник: Code review 2026-06-30 (neyrogovnarik), fix в handlers/bookings.go + repository/bookings.go

#golang #backend #state-machine #concurrency #bug