2ca47eb4df
Backend now owns the turn-cutoff and pause guards the order tab relies on: the scheduler flips runtime_status between generation_in_progress and running around every engine tick, a failed tick auto-pauses the game through OnRuntimeSnapshot, and a new game.paused notification kind fans out alongside game.turn.ready. The user-games handlers reject submits with HTTP 409 turn_already_closed or game_paused depending on the runtime state. UI delegates auto-sync to a new OrderQueue: offline detection, single retry on reconnect, conflict / paused classification. OrderDraftStore surfaces conflictBanner / pausedBanner runes, clears them on local mutation or on a game.turn.ready push via resetForNewTurn. The order tab renders the matching banners and the new conflict per-row badge; i18n bundles cover en + ru. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
69 lines
2.5 KiB
Go
69 lines
2.5 KiB
Go
// Package httperr defines the standard JSON error envelope shared by every
|
|
// backend HTTP middleware and handler.
|
|
//
|
|
// The envelope shape is fixed by `backend/openapi.yaml` (`ErrorResponse`) and
|
|
// must remain identical across the public, user, admin, and internal route
|
|
// groups so that callers can parse failures uniformly.
|
|
package httperr
|
|
|
|
import (
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// Error code values that appear in the JSON envelope `error.code` field. They
|
|
// are documented in `backend/openapi.yaml` and form the closed set of stable
|
|
// machine-readable failure markers.
|
|
const (
|
|
CodeNotImplemented = "not_implemented"
|
|
CodeInvalidRequest = "invalid_request"
|
|
CodeUnauthorized = "unauthorized"
|
|
CodeForbidden = "forbidden"
|
|
CodeNotFound = "not_found"
|
|
CodeConflict = "conflict"
|
|
CodeMethodNotAllowed = "method_not_allowed"
|
|
CodeInternalError = "internal_error"
|
|
CodeServiceUnavailable = "service_unavailable"
|
|
|
|
// CodeTurnAlreadyClosed marks a user-games command or order rejection
|
|
// caused by the backend's turn-cutoff guard: the request arrived
|
|
// after the active turn started generating (runtime status
|
|
// `generation_in_progress` / `generation_failed` / `engine_unreachable`)
|
|
// and the engine no longer accepts writes for the closing turn. The
|
|
// caller is expected to wait for the next `game.turn.ready` push and
|
|
// resubmit against the new turn.
|
|
CodeTurnAlreadyClosed = "turn_already_closed"
|
|
|
|
// CodeGamePaused marks a user-games command or order rejection caused
|
|
// by the lobby-side game lifecycle: the game is in `paused`,
|
|
// `finished`, or any other status that does not accept writes. The
|
|
// caller is expected to wait for the game to resume before
|
|
// resubmitting.
|
|
CodeGamePaused = "game_paused"
|
|
)
|
|
|
|
// Body stores the inner `error` object of the standard envelope.
|
|
type Body struct {
|
|
// Code is the stable machine-readable failure marker.
|
|
Code string `json:"code"`
|
|
|
|
// Message is the human-readable client-safe failure description.
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
// Response wraps Body in the documented `{"error":{...}}` shape.
|
|
type Response struct {
|
|
Error Body `json:"error"`
|
|
}
|
|
|
|
// Abort writes the standard JSON error envelope with statusCode and aborts the
|
|
// gin handler chain. It is the single helper every middleware and handler must
|
|
// use to emit a failure response.
|
|
func Abort(c *gin.Context, statusCode int, code, message string) {
|
|
c.AbortWithStatusJSON(statusCode, Response{
|
|
Error: Body{
|
|
Code: code,
|
|
Message: message,
|
|
},
|
|
})
|
|
}
|