ui: plan 01-27 done #1
@@ -14,7 +14,6 @@ import (
|
|||||||
"galaxy/backend/internal/server/httperr"
|
"galaxy/backend/internal/server/httperr"
|
||||||
"galaxy/backend/internal/server/middleware/userid"
|
"galaxy/backend/internal/server/middleware/userid"
|
||||||
"galaxy/backend/internal/telemetry"
|
"galaxy/backend/internal/telemetry"
|
||||||
"galaxy/model/order"
|
|
||||||
gamerest "galaxy/model/rest"
|
gamerest "galaxy/model/rest"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -26,8 +25,8 @@ import (
|
|||||||
// `engineclient` against running engine containers.
|
// `engineclient` against running engine containers.
|
||||||
type UserGamesHandlers struct {
|
type UserGamesHandlers struct {
|
||||||
runtime *runtime.Service
|
runtime *runtime.Service
|
||||||
engine *engineclient.Client
|
engine *engineclient.Client
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserGamesHandlers constructs the handler set. When runtime or
|
// NewUserGamesHandlers constructs the handler set. When runtime or
|
||||||
@@ -123,7 +122,6 @@ func (h *UserGamesHandlers) Orders() gin.HandlerFunc {
|
|||||||
// handler. Per ARCHITECTURE.md §9 backend is the only caller
|
// handler. Per ARCHITECTURE.md §9 backend is the only caller
|
||||||
// of the engine, so the body never carries a client-supplied
|
// of the engine, so the body never carries a client-supplied
|
||||||
// actor.
|
// actor.
|
||||||
_ = order.Order{}
|
|
||||||
payload, err := rebindActor(body, mapping.RaceName)
|
payload, err := rebindActor(body, mapping.RaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be a JSON object")
|
httperr.Abort(c, http.StatusBadRequest, httperr.CodeInvalidRequest, "request body must be a JSON object")
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"galaxy/game/internal/model/game"
|
"galaxy/game/internal/model/game"
|
||||||
|
|
||||||
@@ -47,10 +48,10 @@ type Repo interface {
|
|||||||
LoadReport(uint, uuid.UUID) (*report.Report, error)
|
LoadReport(uint, uuid.UUID) (*report.Report, error)
|
||||||
|
|
||||||
// SaveOrder stores order for given turn
|
// SaveOrder stores order for given turn
|
||||||
SaveOrder(uint, uuid.UUID, *order.Order) error
|
SaveOrder(uint, uuid.UUID, *order.UserGamesOrder) error
|
||||||
|
|
||||||
// LoadOrder loads order for specific turn and player id
|
// LoadOrder loads order for specific turn and player id
|
||||||
LoadOrder(uint, uuid.UUID) (*order.Order, bool, error)
|
LoadOrder(uint, uuid.UUID) (*order.UserGamesOrder, bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ctrl interface {
|
type Ctrl interface {
|
||||||
@@ -126,14 +127,22 @@ func ExecuteCommand(configure func(*Param), consumer func(c Ctrl) error) (err er
|
|||||||
return ec.executeCommand(func(c *Controller) error { return consumer(c) })
|
return ec.executeCommand(func(c *Controller) error { return consumer(c) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateOrder(configure func(*Param), actor string, cmd ...order.DecodableCommand) (err error) {
|
func ValidateOrder(configure func(*Param), actor string, cmd ...order.DecodableCommand) (*order.UserGamesOrder, error) {
|
||||||
ec, err := NewRepoController(configure)
|
ec, err := NewRepoController(configure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
return ec.validateOrder(actor, cmd...)
|
return ec.validateOrder(actor, cmd...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FetchOrder(configure func(*Param), actor string, turn uint) (order *order.UserGamesOrder, ok bool, err error) {
|
||||||
|
ec, err := NewRepoController(configure)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
return ec.fetchOrder(actor, turn)
|
||||||
|
}
|
||||||
|
|
||||||
func BanishRace(configure func(*Param), actor string) error {
|
func BanishRace(configure func(*Param), actor string) error {
|
||||||
ec, err := NewRepoController(configure)
|
ec, err := NewRepoController(configure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -213,8 +222,8 @@ func (ec *RepoController) NewGameController(g *game.Game) *Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *RepoController) validateOrder(actor string, cmd ...order.DecodableCommand) (err error) {
|
func (ec *RepoController) validateOrder(actor string, cmd ...order.DecodableCommand) (o *order.UserGamesOrder, err error) {
|
||||||
return ec.executeSafe(func(t uint, c *Controller) error {
|
err = ec.executeSafe(func(t uint, c *Controller) error {
|
||||||
id, err := c.RaceID(actor)
|
id, err := c.RaceID(actor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -223,10 +232,33 @@ func (ec *RepoController) validateOrder(actor string, cmd ...order.DecodableComm
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
o := &order.Order{Commands: make([]order.DecodableCommand, len(cmd))}
|
o = &order.UserGamesOrder{
|
||||||
|
GameID: c.Cache.g.ID,
|
||||||
|
UpdatedAt: time.Now().UTC().UnixMilli(),
|
||||||
|
Commands: make([]order.DecodableCommand, len(cmd)),
|
||||||
|
}
|
||||||
copy(o.Commands, cmd)
|
copy(o.Commands, cmd)
|
||||||
return ec.Repo.SaveOrder(t, id, o)
|
return ec.Repo.SaveOrder(t, id, o)
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ec *RepoController) fetchOrder(actor string, turn uint) (order *order.UserGamesOrder, ok bool, err error) {
|
||||||
|
err = ec.executeSafe(func(t uint, c *Controller) error {
|
||||||
|
id, err := c.RaceID(actor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
order, ok, err = ec.Repo.LoadOrder(turn, id)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *RepoController) loadReport(actor string, turn uint) (r *report.Report, err error) {
|
func (ec *RepoController) loadReport(actor string, turn uint) (r *report.Report, err error) {
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ func (c *Controller) applyCommand(actor string, cmd order.DecodableCommand) (err
|
|||||||
|
|
||||||
func (c *Controller) applyOrders(t uint) error {
|
func (c *Controller) applyOrders(t uint) error {
|
||||||
raceOrder := make(map[int][]order.DecodableCommand)
|
raceOrder := make(map[int][]order.DecodableCommand)
|
||||||
|
raceOrderUpdated := make(map[int]int64)
|
||||||
commandRace := make(map[string]string)
|
commandRace := make(map[string]string)
|
||||||
challenge := make(map[string]*order.CommandShipGroupUnload)
|
challenge := make(map[string]*order.CommandShipGroupUnload)
|
||||||
cmdApplied := make(map[string]bool)
|
cmdApplied := make(map[string]bool)
|
||||||
@@ -127,6 +128,7 @@ func (c *Controller) applyOrders(t uint) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
raceOrder[ri] = o.Commands
|
raceOrder[ri] = o.Commands
|
||||||
|
raceOrderUpdated[ri] = o.UpdatedAt
|
||||||
for i := range o.Commands {
|
for i := range o.Commands {
|
||||||
commandRace[o.Commands[i].CommandID()] = c.Cache.g.Race[ri].Name
|
commandRace[o.Commands[i].CommandID()] = c.Cache.g.Race[ri].Name
|
||||||
if v, ok := order.AsCommand[*order.CommandShipGroupUnload](o.Commands[i]); ok {
|
if v, ok := order.AsCommand[*order.CommandShipGroupUnload](o.Commands[i]); ok {
|
||||||
@@ -156,10 +158,12 @@ func (c *Controller) applyOrders(t uint) error {
|
|||||||
// any command might fail due to challenged planets colonization
|
// any command might fail due to challenged planets colonization
|
||||||
_ = c.applyCommand(commandRace[cmd.CommandID()], cmd)
|
_ = c.applyCommand(commandRace[cmd.CommandID()], cmd)
|
||||||
}
|
}
|
||||||
}
|
// re-save order to persist possible changed commands result outcome
|
||||||
|
if err := c.Repo.SaveOrder(t, c.Cache.g.Race[ri].ID, &order.UserGamesOrder{
|
||||||
for ri := range c.Cache.listRaceActingIdx() {
|
GameID: c.Cache.g.ID,
|
||||||
if err := c.Repo.SaveOrder(t, c.Cache.g.Race[ri].ID, &order.Order{Commands: raceOrder[ri]}); err != nil {
|
UpdatedAt: raceOrderUpdated[ri],
|
||||||
|
Commands: raceOrder[ri],
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-11
@@ -29,7 +29,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type storedOrder struct {
|
type storedOrder struct {
|
||||||
Commands []json.RawMessage `json:"cmd"`
|
GameID uuid.UUID `json:"game_id"`
|
||||||
|
UpdatedAt int64 `json:"updatedAt"`
|
||||||
|
Commands []json.RawMessage `json:"cmd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o storedOrder) MarshalBinary() (data []byte, err error) {
|
func (o storedOrder) MarshalBinary() (data []byte, err error) {
|
||||||
@@ -201,11 +203,11 @@ func loadReport(s Storage, t uint, id uuid.UUID) (*report.Report, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *repo) SaveOrder(t uint, id uuid.UUID, o *order.Order) error {
|
func (r *repo) SaveOrder(t uint, id uuid.UUID, o *order.UserGamesOrder) error {
|
||||||
return saveOrder(r.s, t, id, o)
|
return saveOrder(r.s, t, id, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveOrder(s Storage, t uint, id uuid.UUID, o *order.Order) error {
|
func saveOrder(s Storage, t uint, id uuid.UUID, o *order.UserGamesOrder) error {
|
||||||
path := OrderDir(t, id)
|
path := OrderDir(t, id)
|
||||||
if err := s.WriteSafe(path, o); err != nil {
|
if err := s.WriteSafe(path, o); err != nil {
|
||||||
return NewStorageError(err)
|
return NewStorageError(err)
|
||||||
@@ -213,11 +215,11 @@ func saveOrder(s Storage, t uint, id uuid.UUID, o *order.Order) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *repo) LoadOrder(t uint, id uuid.UUID) (*order.Order, bool, error) {
|
func (r *repo) LoadOrder(t uint, id uuid.UUID) (*order.UserGamesOrder, bool, error) {
|
||||||
return loadOrder(r.s, t, id)
|
return loadOrder(r.s, t, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadOrder(s Storage, t uint, id uuid.UUID) (*order.Order, bool, error) {
|
func loadOrder(s Storage, t uint, id uuid.UUID) (*order.UserGamesOrder, bool, error) {
|
||||||
path := OrderDir(t, id)
|
path := OrderDir(t, id)
|
||||||
|
|
||||||
exist, err := s.Exists(path)
|
exist, err := s.Exists(path)
|
||||||
@@ -228,17 +230,21 @@ func loadOrder(s Storage, t uint, id uuid.UUID) (*order.Order, bool, error) {
|
|||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := new(storedOrder)
|
stored := new(storedOrder)
|
||||||
if err := s.ReadSafe(path, cmd); err != nil {
|
if err := s.ReadSafe(path, stored); err != nil {
|
||||||
return nil, false, NewStorageError(err)
|
return nil, false, NewStorageError(err)
|
||||||
}
|
}
|
||||||
result := &order.Order{Commands: make([]order.DecodableCommand, len(cmd.Commands))}
|
result := &order.UserGamesOrder{
|
||||||
if len(cmd.Commands) == 0 {
|
GameID: stored.GameID,
|
||||||
|
UpdatedAt: stored.UpdatedAt,
|
||||||
|
Commands: make([]order.DecodableCommand, len(stored.Commands)),
|
||||||
|
}
|
||||||
|
if len(stored.Commands) == 0 {
|
||||||
return nil, false, errors.New("no commands were stored")
|
return nil, false, errors.New("no commands were stored")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range cmd.Commands {
|
for i := range stored.Commands {
|
||||||
command, err := ParseOrder(cmd.Commands[i], nil)
|
command, err := ParseOrder(stored.Commands[i], nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadOrder_T(s Storage, t uint, id uuid.UUID) (*order.Order, bool, error) {
|
func LoadOrder_T(s Storage, t uint, id uuid.UUID) (*order.UserGamesOrder, bool, error) {
|
||||||
return loadOrder(s, t, id)
|
return loadOrder(s, t, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveOrder_T(s Storage, t uint, id uuid.UUID, o *order.Order) error {
|
func SaveOrder_T(s Storage, t uint, id uuid.UUID, o *order.UserGamesOrder) error {
|
||||||
return saveOrder(s, t, id, o)
|
return saveOrder(s, t, id, o)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package repo_test
|
|||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"galaxy/model/order"
|
"galaxy/model/order"
|
||||||
|
|
||||||
@@ -18,7 +19,11 @@ func TestSaveOrder(t *testing.T) {
|
|||||||
s, err := fs.NewFileStorage(root)
|
s, err := fs.NewFileStorage(root)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
id := uuid.New()
|
id := uuid.New()
|
||||||
o := &order.Order{
|
gameID := uuid.New()
|
||||||
|
now := time.Now().UTC().UnixMilli()
|
||||||
|
o := &order.UserGamesOrder{
|
||||||
|
GameID: gameID,
|
||||||
|
UpdatedAt: now,
|
||||||
Commands: []order.DecodableCommand{
|
Commands: []order.DecodableCommand{
|
||||||
&order.CommandRaceVote{
|
&order.CommandRaceVote{
|
||||||
CommandMeta: order.CommandMeta{
|
CommandMeta: order.CommandMeta{
|
||||||
@@ -87,17 +92,19 @@ func TestSaveOrder(t *testing.T) {
|
|||||||
LoadOrderTest(t, s, root, turn, id, o)
|
LoadOrderTest(t, s, root, turn, id, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadOrderTest(t *testing.T, s repo.Storage, root string, turn uint, id uuid.UUID, expected *order.Order) {
|
func LoadOrderTest(t *testing.T, s repo.Storage, root string, turn uint, id uuid.UUID, expected *order.UserGamesOrder) {
|
||||||
o, ok, err := repo.LoadOrder_T(s, turn, id)
|
o, ok, err := repo.LoadOrder_T(s, turn, id)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Len(t, o.Commands, 5)
|
assert.Len(t, o.Commands, 5)
|
||||||
|
assert.Equal(t, expected.GameID, o.GameID)
|
||||||
|
assert.Equal(t, expected.UpdatedAt, o.UpdatedAt)
|
||||||
assert.ElementsMatch(t, expected.Commands, o.Commands)
|
assert.ElementsMatch(t, expected.Commands, o.Commands)
|
||||||
|
|
||||||
CommandResultTest(t, o)
|
CommandResultTest(t, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CommandResultTest(t *testing.T, o *order.Order) {
|
func CommandResultTest(t *testing.T, o *order.UserGamesOrder) {
|
||||||
assert.NotEmpty(t, o.Commands)
|
assert.NotEmpty(t, o.Commands)
|
||||||
for i := range o.Commands {
|
for i := range o.Commands {
|
||||||
if v, ok := order.AsCommand[*order.CommandRaceVote](o.Commands[i]); ok {
|
if v, ok := order.AsCommand[*order.CommandRaceVote](o.Commands[i]); ok {
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func CommandHandler(c *gin.Context, executor CommandExecutor) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusAccepted)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCommand(actor string, c json.RawMessage) (Command, error) {
|
func parseCommand(actor string, c json.RawMessage) (Command, error) {
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ type CommandExecutor interface {
|
|||||||
GameState() (rest.StateResponse, error)
|
GameState() (rest.StateResponse, error)
|
||||||
BanishRace(string) error
|
BanishRace(string) error
|
||||||
LoadReport(actor string, turn uint) (*report.Report, error)
|
LoadReport(actor string, turn uint) (*report.Report, error)
|
||||||
|
// Execute is reserved for future use; any API request for orders should use ValidateOrder
|
||||||
Execute(cmd ...Command) error
|
Execute(cmd ...Command) error
|
||||||
ValidateOrder(actor string, cmd ...order.DecodableCommand) error
|
ValidateOrder(actor string, cmd ...order.DecodableCommand) (*order.UserGamesOrder, error)
|
||||||
|
FetchOrder(actor string, turn uint) (*order.UserGamesOrder, bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Command func(controller.Ctrl) error
|
type Command func(controller.Ctrl) error
|
||||||
@@ -76,10 +78,14 @@ func (e *executor) Execute(cmd ...Command) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *executor) ValidateOrder(actor string, cmd ...order.DecodableCommand) error {
|
func (e *executor) ValidateOrder(actor string, cmd ...order.DecodableCommand) (*order.UserGamesOrder, error) {
|
||||||
return controller.ValidateOrder(e.cfg, actor, cmd...)
|
return controller.ValidateOrder(e.cfg, actor, cmd...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *executor) FetchOrder(actor string, turn uint) (*order.UserGamesOrder, bool, error) {
|
||||||
|
return controller.FetchOrder(e.cfg, actor, turn)
|
||||||
|
}
|
||||||
|
|
||||||
func (e *executor) GenerateGame(races []string) (rest.StateResponse, error) {
|
func (e *executor) GenerateGame(races []string) (rest.StateResponse, error) {
|
||||||
s, err := controller.GenerateGame(e.cfg, races)
|
s, err := controller.GenerateGame(e.cfg, races)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func OrderHandler(c *gin.Context, executor CommandExecutor) {
|
func PutOrderHandler(c *gin.Context, executor CommandExecutor) {
|
||||||
var cmd rest.Command
|
var cmd rest.Command
|
||||||
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
|
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
|
||||||
return
|
return
|
||||||
@@ -31,9 +31,38 @@ func OrderHandler(c *gin.Context, executor CommandExecutor) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if errorResponse(c, executor.ValidateOrder(cmd.Actor, commands...)) {
|
result, err := executor.ValidateOrder(cmd.Actor, commands...)
|
||||||
|
if errorResponse(c, err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Status(http.StatusNoContent)
|
c.JSON(http.StatusAccepted, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
type orderParam struct {
|
||||||
|
Player string `form:"player" binding:"required,notblank"`
|
||||||
|
Turn int `form:"turn" binding:"gte=0"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetOrderHandler(c *gin.Context, executor CommandExecutor) {
|
||||||
|
p := &orderParam{}
|
||||||
|
err := c.ShouldBindQuery(p)
|
||||||
|
if errorResponse(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
order, ok, err := executor.FetchOrder(p.Player, uint(p.Turn))
|
||||||
|
if errorResponse(c, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
// there was no order previously sent by player
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
var cmd rest.Command
|
||||||
|
if errorResponse(c, c.ShouldBindJSON(&cmd)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, order)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,8 +74,11 @@ func setupRouter(executor handler.CommandExecutor) *gin.Engine {
|
|||||||
groupAdmin.POST("/race/banish", func(ctx *gin.Context) { handler.BanishHandler(ctx, executor) })
|
groupAdmin.POST("/race/banish", func(ctx *gin.Context) { handler.BanishHandler(ctx, executor) })
|
||||||
|
|
||||||
groupV1.GET("/report", func(ctx *gin.Context) { handler.ReportHandler(ctx, executor) })
|
groupV1.GET("/report", func(ctx *gin.Context) { handler.ReportHandler(ctx, executor) })
|
||||||
|
groupV1.PUT("/order", func(ctx *gin.Context) { handler.PutOrderHandler(ctx, executor) })
|
||||||
|
groupV1.GET("/order", func(ctx *gin.Context) { handler.GetOrderHandler(ctx, executor) })
|
||||||
|
|
||||||
|
// /command is reserved for future use; any API request for orders should use /order
|
||||||
groupV1.PUT("/command", LimitMiddleware(1), func(ctx *gin.Context) { handler.CommandHandler(ctx, executor) })
|
groupV1.PUT("/command", LimitMiddleware(1), func(ctx *gin.Context) { handler.CommandHandler(ctx, executor) })
|
||||||
groupV1.PUT("/order", func(ctx *gin.Context) { handler.OrderHandler(ctx, executor) })
|
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
commandNoErrorsStatus = http.StatusNoContent
|
commandNoErrorsStatus = http.StatusAccepted
|
||||||
commandDefaultActor = "Gorlum"
|
commandDefaultActor = "Gorlum"
|
||||||
apiCommandMethod = "PUT"
|
apiCommandMethod = "PUT"
|
||||||
apiCommandPath = "/api/v1/command"
|
apiCommandPath = "/api/v1/command"
|
||||||
@@ -34,9 +34,13 @@ type dummyExecutor struct {
|
|||||||
CommandsExecuted int
|
CommandsExecuted int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *dummyExecutor) ValidateOrder(actor string, cmd ...order.DecodableCommand) error {
|
func (e *dummyExecutor) ValidateOrder(actor string, cmd ...order.DecodableCommand) (*order.UserGamesOrder, error) {
|
||||||
e.CommandsExecuted = len(cmd)
|
e.CommandsExecuted = len(cmd)
|
||||||
return nil
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *dummyExecutor) FetchOrder(actor string, turn uint) (*order.UserGamesOrder, bool, error) {
|
||||||
|
return nil, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *dummyExecutor) Execute(command ...handler.Command) error {
|
func (e *dummyExecutor) Execute(command ...handler.Command) error {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ type GameState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type GameData struct {
|
type GameData struct {
|
||||||
Turn uint `json:"turn"`
|
Turn uint `json:"turn"`
|
||||||
Report report.Report `json:"report"`
|
Report report.Report `json:"report"`
|
||||||
Order *order.Order `json:"order,omitempty"`
|
Order *order.UserGamesOrder `json:"order,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,23 +40,17 @@ type UserGamesOrder struct {
|
|||||||
|
|
||||||
// UpdatedAt is the client-side timestamp used for stale-order
|
// UpdatedAt is the client-side timestamp used for stale-order
|
||||||
// detection on the engine side.
|
// detection on the engine side.
|
||||||
UpdatedAt int `json:"updatedAt"`
|
UpdatedAt int64 `json:"updatedAt"`
|
||||||
|
|
||||||
// Commands is the player order batch.
|
// Commands is the player order batch.
|
||||||
Commands []DecodableCommand `json:"cmd"`
|
Commands []DecodableCommand `json:"cmd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Order struct {
|
func (o UserGamesOrder) MarshalBinary() (data []byte, err error) {
|
||||||
// TODO: check with already stored order, if any, and generate an error, if newer order exists
|
|
||||||
UpdatedAt int `json:"updatedAt"`
|
|
||||||
Commands []DecodableCommand `json:"cmd"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o Order) MarshalBinary() (data []byte, err error) {
|
|
||||||
return json.Marshal(&o)
|
return json.Marshal(&o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Order) UnmarshalBinary(data []byte) error {
|
func (o *UserGamesOrder) UnmarshalBinary(data []byte) error {
|
||||||
return json.Unmarshal(data, o)
|
return json.Unmarshal(data, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-10
@@ -17,6 +17,8 @@ import (
|
|||||||
"galaxy/model/order"
|
"galaxy/model/order"
|
||||||
"galaxy/model/report"
|
"galaxy/model/report"
|
||||||
"galaxy/util"
|
"galaxy/util"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -62,7 +64,8 @@ type fsStorage struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type storedOrder struct {
|
type storedOrder struct {
|
||||||
UpdatedAt int `json:"updatedAt"`
|
GameID uuid.UUID `json:"game_id"`
|
||||||
|
UpdatedAt int64 `json:"updatedAt"`
|
||||||
Commands []json.RawMessage `json:"cmd"`
|
Commands []json.RawMessage `json:"cmd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +158,7 @@ func (s *fsStorage) SaveReportAsync(id client.GameID, turn uint, rep report.Repo
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fsStorage) LoadOrderAsync(id client.GameID, turn uint, callback func(order.Order, error)) {
|
func (s *fsStorage) LoadOrderAsync(id client.GameID, turn uint, callback func(order.UserGamesOrder, error)) {
|
||||||
go func() {
|
go func() {
|
||||||
o, err := s.loadOrderSync(id, turn)
|
o, err := s.loadOrderSync(id, turn)
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
@@ -164,7 +167,7 @@ func (s *fsStorage) LoadOrderAsync(id client.GameID, turn uint, callback func(or
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fsStorage) SaveOrderAsync(id client.GameID, turn uint, o order.Order, callback func(error)) {
|
func (s *fsStorage) SaveOrderAsync(id client.GameID, turn uint, o order.UserGamesOrder, callback func(error)) {
|
||||||
go func() {
|
go func() {
|
||||||
err := s.saveOrderSync(id, turn, o)
|
err := s.saveOrderSync(id, turn, o)
|
||||||
if callback != nil {
|
if callback != nil {
|
||||||
@@ -320,18 +323,18 @@ func (s *fsStorage) saveReportSync(id client.GameID, turn uint, rep report.Repor
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fsStorage) loadOrderSync(id client.GameID, turn uint) (order.Order, error) {
|
func (s *fsStorage) loadOrderSync(id client.GameID, turn uint) (order.UserGamesOrder, error) {
|
||||||
gameData, err := s.loadGameDataSync(id, turn)
|
gameData, err := s.loadGameDataSync(id, turn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return order.Order{}, classifyStorageError(err)
|
return order.UserGamesOrder{}, classifyStorageError(err)
|
||||||
}
|
}
|
||||||
if gameData.Order == nil {
|
if gameData.Order == nil {
|
||||||
return order.Order{}, classifyStorageError(fmt.Errorf("load order for game %q turn %d: %w", id, turn, os.ErrNotExist))
|
return order.UserGamesOrder{}, classifyStorageError(fmt.Errorf("load order for game %q turn %d: %w", id, turn, os.ErrNotExist))
|
||||||
}
|
}
|
||||||
return *gameData.Order, nil
|
return *gameData.Order, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fsStorage) saveOrderSync(id client.GameID, turn uint, o order.Order) error {
|
func (s *fsStorage) saveOrderSync(id client.GameID, turn uint, o order.UserGamesOrder) error {
|
||||||
absPath, err := s.resolvePath(gameTurnFilePath(id, turn))
|
absPath, err := s.resolvePath(gameTurnFilePath(id, turn))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return classifyStorageError(err)
|
return classifyStorageError(err)
|
||||||
@@ -474,8 +477,9 @@ func (d storedGameData) toGameData() (client.GameData, error) {
|
|||||||
return gameData, nil
|
return gameData, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeStoredOrder(o order.Order) (storedOrder, error) {
|
func makeStoredOrder(o order.UserGamesOrder) (storedOrder, error) {
|
||||||
result := storedOrder{
|
result := storedOrder{
|
||||||
|
GameID: o.GameID,
|
||||||
UpdatedAt: o.UpdatedAt,
|
UpdatedAt: o.UpdatedAt,
|
||||||
Commands: make([]json.RawMessage, len(o.Commands)),
|
Commands: make([]json.RawMessage, len(o.Commands)),
|
||||||
}
|
}
|
||||||
@@ -489,12 +493,13 @@ func makeStoredOrder(o order.Order) (storedOrder, error) {
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *storedOrder) toOrder() (*order.Order, error) {
|
func (o *storedOrder) toOrder() (*order.UserGamesOrder, error) {
|
||||||
if o == nil {
|
if o == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result := &order.Order{
|
result := &order.UserGamesOrder{
|
||||||
|
GameID: o.GameID,
|
||||||
UpdatedAt: o.UpdatedAt,
|
UpdatedAt: o.UpdatedAt,
|
||||||
Commands: make([]order.DecodableCommand, len(o.Commands)),
|
Commands: make([]order.DecodableCommand, len(o.Commands)),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"galaxy/model/client"
|
"galaxy/model/client"
|
||||||
"galaxy/model/order"
|
"galaxy/model/order"
|
||||||
"galaxy/model/report"
|
"galaxy/model/report"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const testTimeout = time.Second
|
const testTimeout = time.Second
|
||||||
@@ -137,9 +139,9 @@ func TestReportAndOrderRoundTripAsync(t *testing.T) {
|
|||||||
t.Fatalf("loaded report mismatch\nwant: %#v\ngot: %#v", updatedReport, gotReport.value)
|
t.Fatalf("loaded report mismatch\nwant: %#v\ngot: %#v", updatedReport, gotReport.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadOrderDone := make(chan callbackResult[order.Order], 1)
|
loadOrderDone := make(chan callbackResult[order.UserGamesOrder], 1)
|
||||||
s.LoadOrderAsync(id, turn, func(got order.Order, err error) {
|
s.LoadOrderAsync(id, turn, func(got order.UserGamesOrder, err error) {
|
||||||
loadOrderDone <- callbackResult[order.Order]{value: got, err: err}
|
loadOrderDone <- callbackResult[order.UserGamesOrder]{value: got, err: err}
|
||||||
})
|
})
|
||||||
gotOrder := waitResult(t, loadOrderDone)
|
gotOrder := waitResult(t, loadOrderDone)
|
||||||
if gotOrder.err != nil {
|
if gotOrder.err != nil {
|
||||||
@@ -529,8 +531,9 @@ func sampleReport(turn uint, race string) report.Report {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sampleOrder() order.Order {
|
func sampleOrder() order.UserGamesOrder {
|
||||||
return order.Order{
|
return order.UserGamesOrder{
|
||||||
|
GameID: uuid.New(),
|
||||||
UpdatedAt: 1700,
|
UpdatedAt: 1700,
|
||||||
Commands: []order.DecodableCommand{
|
Commands: []order.DecodableCommand{
|
||||||
&order.CommandPlanetRename{
|
&order.CommandPlanetRename{
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ type UIStorage interface {
|
|||||||
// LoadOrderAsync loads a [order.Order] for a given [model.GameID] and turn number from filesystem asynchronously.
|
// LoadOrderAsync loads a [order.Order] for a given [model.GameID] and turn number from filesystem asynchronously.
|
||||||
// Passed callback func will will accept non-nil error in case of I/O or decoding errors occuried,
|
// Passed callback func will will accept non-nil error in case of I/O or decoding errors occuried,
|
||||||
// otherwise callback func accepts loaded [order.Order].
|
// otherwise callback func accepts loaded [order.Order].
|
||||||
LoadOrderAsync(client.GameID, uint, func(order.Order, error))
|
LoadOrderAsync(client.GameID, uint, func(order.UserGamesOrder, error))
|
||||||
|
|
||||||
// SaveOrderAsync stores given [order.Order] for a given [model.GameID] and turn number at the filesystem asynchronously.
|
// SaveOrderAsync stores given [order.Order] for a given [model.GameID] and turn number at the filesystem asynchronously.
|
||||||
// I/O or encoding error may occur, it that case callback func will be called with non-nil error.
|
// I/O or encoding error may occur, it that case callback func will be called with non-nil error.
|
||||||
SaveOrderAsync(client.GameID, uint, order.Order, func(error))
|
SaveOrderAsync(client.GameID, uint, order.UserGamesOrder, func(error))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -916,13 +916,13 @@ func PayloadToUserGamesOrder(data []byte) (result *model.UserGamesOrder, err err
|
|||||||
if gameID == nil {
|
if gameID == nil {
|
||||||
return nil, errors.New("decode user games order payload: game_id is missing")
|
return nil, errors.New("decode user games order payload: game_id is missing")
|
||||||
}
|
}
|
||||||
updatedAt, convErr := int64ToInt(flat.UpdatedAt(), "updated_at")
|
// updatedAt, convErr := int64ToInt(flat.UpdatedAt(), "updated_at")
|
||||||
if convErr != nil {
|
// if convErr != nil {
|
||||||
return nil, fmt.Errorf("decode user games order payload: %w", convErr)
|
// return nil, fmt.Errorf("decode user games order payload: %w", convErr)
|
||||||
}
|
// }
|
||||||
out := &model.UserGamesOrder{
|
out := &model.UserGamesOrder{
|
||||||
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
|
GameID: uuidFromHiLo(gameID.Hi(), gameID.Lo()),
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: flat.UpdatedAt(),
|
||||||
}
|
}
|
||||||
count := flat.CommandsLength()
|
count := flat.CommandsLength()
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user