From cb42bdc8d05ebfdbcbf02d520a9cb2679bfe6528 Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Sat, 13 Mar 2021 15:19:55 -0600 Subject: [PATCH] to packages --- src/server/{ => config}/config.go | 28 +++- src/server/config_test.go | 27 --- src/server/consts/errors.go | 35 ++++ src/server/entity/card.go | 6 + src/server/entity/currency.go | 3 + src/server/entity/game.go | 14 ++ src/server/entity/player.go | 15 ++ .../{game_test.go => entity/player_test.go} | 2 +- src/server/entity/players.go | 7 + src/server/errors.go | 7 - src/server/game.go | 41 ----- src/server/game/master.go | 66 ++++++++ .../master_test.go} | 23 ++- src/server/gamemaster.go | 52 ------ src/server/main.go | 12 +- src/server/server.go | 156 ------------------ src/server/server_test.go | 81 --------- src/server/{ => storage}/lockmap.go | 2 +- src/server/{ => storage}/lockmap_test.go | 2 +- src/server/{ => storage}/storage.go | 18 +- 20 files changed, 202 insertions(+), 395 deletions(-) rename src/server/{ => config}/config.go (70%) delete mode 100644 src/server/config_test.go create mode 100644 src/server/consts/errors.go create mode 100644 src/server/entity/card.go create mode 100644 src/server/entity/currency.go create mode 100644 src/server/entity/game.go create mode 100644 src/server/entity/player.go rename src/server/{game_test.go => entity/player_test.go} (91%) create mode 100644 src/server/entity/players.go delete mode 100644 src/server/errors.go delete mode 100644 src/server/game.go create mode 100644 src/server/game/master.go rename src/server/{gamemaster_test.go => game/master_test.go} (70%) delete mode 100644 src/server/gamemaster.go delete mode 100644 src/server/server.go delete mode 100644 src/server/server_test.go rename src/server/{ => storage}/lockmap.go (97%) rename src/server/{ => storage}/lockmap_test.go (91%) rename src/server/{ => storage}/storage.go (60%) diff --git a/src/server/config.go b/src/server/config/config.go similarity index 70% rename from src/server/config.go rename to src/server/config/config.go index 589e680..c4df3c7 100644 --- a/src/server/config.go +++ b/src/server/config/config.go @@ -1,14 +1,18 @@ -package main +package config import ( + "io/ioutil" "local/args" "local/storage" + "os" + "path" "strings" + "testing" ) type Config struct { Game struct { - db storage.DB + DB storage.DB } Server struct { Port int @@ -42,7 +46,7 @@ func NewConfig() Config { } config := Config{} - config.Game.db = db + config.Game.DB = db config.Server.Port = as.GetInt("p") config.Server.File.Root = as.GetString("root") config.Server.File.Prefix = "/" + strings.TrimPrefix(as.GetString("root-http-prefix"), "/") @@ -50,3 +54,21 @@ func NewConfig() Config { return config } + +func NewTestConfig(t *testing.T) Config { + d := t.TempDir() + + config := Config{} + config.Game.DB = storage.NewMap() + config.Server.Port = 9999 + config.Server.File.Root = d + config.Server.File.Prefix = "/file" + config.Server.API.Prefix = "/api" + + err := ioutil.WriteFile(path.Join(d, "index.html"), []byte("Hello, world"), os.ModePerm) + if err != nil { + t.Fatal(err) + } + + return config +} diff --git a/src/server/config_test.go b/src/server/config_test.go deleted file mode 100644 index 9805c03..0000000 --- a/src/server/config_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "io/ioutil" - "local/storage" - "os" - "path" - "testing" -) - -func mockConfig(t *testing.T) Config { - d := t.TempDir() - - config := Config{} - config.Game.db = storage.NewMap() - config.Server.Port = 9999 - config.Server.File.Root = d - config.Server.File.Prefix = "/file" - config.Server.API.Prefix = "/api" - - err := ioutil.WriteFile(path.Join(d, "index.html"), []byte("Hello, world"), os.ModePerm) - if err != nil { - t.Fatal(err) - } - - return config -} diff --git a/src/server/consts/errors.go b/src/server/consts/errors.go new file mode 100644 index 0000000..0a82ab7 --- /dev/null +++ b/src/server/consts/errors.go @@ -0,0 +1,35 @@ +package consts + +import ( + "net/http" +) + +var ( + ErrGameExists = NewCodeError("game exists", http.StatusConflict) +) + +type CodeError struct { + Err string + Status int +} + +func NewCodeError(err string, status int) CodeError { + return CodeError{ + Err: err, + Status: status, + } +} + +func (ce CodeError) Error() string { + if ce.Err == "" { + return "unspecified error occurred" + } + return ce.Err +} + +func (ce CodeError) Code() int { + if ce.Status == 0 { + return http.StatusInternalServerError + } + return ce.Status +} diff --git a/src/server/entity/card.go b/src/server/entity/card.go new file mode 100644 index 0000000..6eadd2e --- /dev/null +++ b/src/server/entity/card.go @@ -0,0 +1,6 @@ +package entity + +type Card struct { + Suit int + Value int +} diff --git a/src/server/entity/currency.go b/src/server/entity/currency.go new file mode 100644 index 0000000..fe33870 --- /dev/null +++ b/src/server/entity/currency.go @@ -0,0 +1,3 @@ +package entity + +type Currency int diff --git a/src/server/entity/game.go b/src/server/entity/game.go new file mode 100644 index 0000000..a659381 --- /dev/null +++ b/src/server/entity/game.go @@ -0,0 +1,14 @@ +package entity + +import "fmt" + +type Game struct { + Pot Currency + Turn int + Players Players + Log string +} + +func (game Game) Equals(gameB Game) bool { + return fmt.Sprint(game) != fmt.Sprint(gameB) +} diff --git a/src/server/entity/player.go b/src/server/entity/player.go new file mode 100644 index 0000000..e9cca05 --- /dev/null +++ b/src/server/entity/player.go @@ -0,0 +1,15 @@ +package entity + +type Player struct { + ID string + Name string + Card Card + Balance Currency + Active bool + Participating bool + Checked bool +} + +func (p Player) Empty() bool { + return p == (Player{}) +} diff --git a/src/server/game_test.go b/src/server/entity/player_test.go similarity index 91% rename from src/server/game_test.go rename to src/server/entity/player_test.go index 571505a..04e2547 100644 --- a/src/server/game_test.go +++ b/src/server/entity/player_test.go @@ -1,4 +1,4 @@ -package main +package entity import ( "testing" diff --git a/src/server/entity/players.go b/src/server/entity/players.go new file mode 100644 index 0000000..68261c0 --- /dev/null +++ b/src/server/entity/players.go @@ -0,0 +1,7 @@ +package entity + +type Players []Player + +func (players *Players) Add(player Player) { + *players = append(*players, player) +} diff --git a/src/server/errors.go b/src/server/errors.go deleted file mode 100644 index 0426817..0000000 --- a/src/server/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "errors" - -var ( - errGameExists = errors.New("cannot create game: already exists") -) diff --git a/src/server/game.go b/src/server/game.go deleted file mode 100644 index 7cc7562..0000000 --- a/src/server/game.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import "fmt" - -type Players []Player - -type Game struct { - Pot Currency - Turn int - Players Players - Log string -} - -type Player struct { - ID string - Name string - Card Card - Balance Currency - Active bool - Participating bool - Checked bool -} - -type Currency int - -type Card struct { - Suit int - Value int -} - -func (game Game) Equals(gameB Game) bool { - return fmt.Sprint(game) != fmt.Sprint(gameB) -} - -func (p Player) Empty() bool { - return p == (Player{}) -} - -func (players *Players) Add(player Player) { - *players = append(*players, player) -} diff --git a/src/server/game/master.go b/src/server/game/master.go new file mode 100644 index 0000000..6eab01a --- /dev/null +++ b/src/server/game/master.go @@ -0,0 +1,66 @@ +package game + +import ( + "local/sandbox/blind-mans-poker/src/server/config" + "local/sandbox/blind-mans-poker/src/server/consts" + "local/sandbox/blind-mans-poker/src/server/entity" + "local/sandbox/blind-mans-poker/src/server/storage" + "testing" +) + +type Master struct { + config config.Config + storage *storage.Storage + locks *storage.RWLockMap +} + +func NewMaster(config config.Config, s *storage.Storage) *Master { + return &Master{ + config: config, + storage: s, + locks: storage.NewRWLockMap(), + } +} + +func (gm *Master) ListGames() ([]string, error) { + return gm.storage.ListGames() +} + +func (gm *Master) GetGame(id string) (entity.Game, error) { + gm.locks.RLock(id) + defer gm.locks.RUnlock(id) + + game, err := gm.storage.GetGame(id) + if game.Players == nil { + game.Players = make(entity.Players, 0) + } + return game, err +} + +func (gm *Master) CreateGame(id string) error { + gm.locks.Lock(id) + defer gm.locks.Unlock(id) + + if _, err := gm.storage.GetGame(id); err == nil { + return consts.ErrGameExists + } + + return gm.storage.CreateGame(id) +} + +func (gm *Master) ReplaceGame(id string, game entity.Game) error { + gm.locks.Lock(id) + defer gm.locks.Unlock(id) + + if _, err := gm.storage.GetGame(id); err != nil { + return err + } + + return gm.storage.ReplaceGame(id, game) +} + +func NewTestMaster(t *testing.T) *Master { + config := config.NewTestConfig(t) + storage := storage.NewStorage(config) + return NewMaster(config, storage) +} diff --git a/src/server/gamemaster_test.go b/src/server/game/master_test.go similarity index 70% rename from src/server/gamemaster_test.go rename to src/server/game/master_test.go index f1cf9e8..a62149a 100644 --- a/src/server/gamemaster_test.go +++ b/src/server/game/master_test.go @@ -1,15 +1,12 @@ -package main +package game -import "testing" +import ( + "local/sandbox/blind-mans-poker/src/server/entity" + "testing" +) -func mockGameMaster(t *testing.T) *GameMaster { - config := mockConfig(t) - storage := NewStorage(config) - return NewGameMaster(config, storage) -} - -func TestGameMasterGetCreateGetList(t *testing.T) { - gm := mockGameMaster(t) +func TestMasterGetCreateGetList(t *testing.T) { + gm := NewTestMaster(t) id := "game" if games, err := gm.ListGames(); err != nil { @@ -33,8 +30,8 @@ func TestGameMasterGetCreateGetList(t *testing.T) { } } -func TestGameMasterUpdate(t *testing.T) { - gm := mockGameMaster(t) +func TestMasterUpdate(t *testing.T) { + gm := NewTestMaster(t) id := "game" err := gm.CreateGame(id) @@ -47,7 +44,7 @@ func TestGameMasterUpdate(t *testing.T) { t.Fatal(err) } - game.Players.Add(Player{ + game.Players.Add(entity.Player{ ID: "hi", }) game.Pot = 123 diff --git a/src/server/gamemaster.go b/src/server/gamemaster.go deleted file mode 100644 index 0904503..0000000 --- a/src/server/gamemaster.go +++ /dev/null @@ -1,52 +0,0 @@ -package main - -type GameMaster struct { - config Config - storage *Storage - locks *RWLockMap -} - -func NewGameMaster(config Config, storage *Storage) *GameMaster { - return &GameMaster{ - config: config, - storage: storage, - locks: NewRWLockMap(), - } -} - -func (gm *GameMaster) ListGames() ([]string, error) { - return gm.storage.ListGames() -} - -func (gm *GameMaster) GetGame(id string) (Game, error) { - gm.locks.RLock(id) - defer gm.locks.RUnlock(id) - - game, err := gm.storage.GetGame(id) - if game.Players == nil { - game.Players = make(Players, 0) - } - return game, err -} - -func (gm *GameMaster) CreateGame(id string) error { - gm.locks.Lock(id) - defer gm.locks.Unlock(id) - - if _, err := gm.storage.GetGame(id); err == nil { - return errGameExists - } - - return gm.storage.CreateGame(id) -} - -func (gm *GameMaster) ReplaceGame(id string, game Game) error { - gm.locks.Lock(id) - defer gm.locks.Unlock(id) - - if _, err := gm.storage.GetGame(id); err != nil { - return err - } - - return gm.storage.ReplaceGame(id, game) -} diff --git a/src/server/main.go b/src/server/main.go index 727564d..cae3912 100644 --- a/src/server/main.go +++ b/src/server/main.go @@ -2,15 +2,19 @@ package main import ( "fmt" + "local/sandbox/blind-mans-poker/src/server/config" + "local/sandbox/blind-mans-poker/src/server/game" + "local/sandbox/blind-mans-poker/src/server/server" + "local/sandbox/blind-mans-poker/src/server/storage" "log" "net/http" ) func main() { - config := NewConfig() - storage := NewStorage(config) - gm := NewGameMaster(config, storage) - server := NewServer(config, gm) + config := config.NewConfig() + storage := storage.NewStorage(config) + gm := game.NewMaster(config, storage) + server := server.NewServer(config, gm) if err := server.Routes(); err != nil { panic(err) } diff --git a/src/server/server.go b/src/server/server.go deleted file mode 100644 index 949c776..0000000 --- a/src/server/server.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "local/router" - "local/storage" - "log" - "net/http" - "strings" -) - -type Server struct { - config Config - gm *GameMaster - *router.Router -} - -func NewServer(config Config, gm *GameMaster) *Server { - return &Server{ - config: config, - gm: gm, - Router: router.New(), - } -} - -func (server *Server) Routes() error { - cases := map[string]map[string]http.HandlerFunc{ - fmt.Sprintf("%s/%s%s", server.config.Server.File.Prefix, router.Wildcard, router.Wildcard): map[string]http.HandlerFunc{ - http.MethodGet: server.File, - }, - fmt.Sprintf("%s/games", server.config.Server.API.Prefix): map[string]http.HandlerFunc{ - http.MethodGet: server.GameList, - }, - fmt.Sprintf("%s/games/%s", server.config.Server.API.Prefix, router.Wildcard): map[string]http.HandlerFunc{ - http.MethodGet: server.GameGet, - http.MethodPost: server.GameInsert, - http.MethodPut: server.GameReplace, - }, - } - - for path, spec := range cases { - log.Println("listening for:", path) - handler := server.ByMethod(spec) - if err := server.Add(path, handler); err != nil { - return err - } - } - - return nil -} - -func (server *Server) ByMethod(m map[string]http.HandlerFunc) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - foo, ok := m[r.Method] - if ok { - foo(w, r) - } else { - http.NotFound(w, r) - } - }) -} - -func (server *Server) File(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.NotFound(w, r) - return - } - s := http.FileServer(http.Dir(server.config.Server.File.Root)) - r.URL.Path = strings.TrimPrefix(r.URL.Path, server.config.Server.File.Prefix) - s.ServeHTTP(w, r) -} - -func (server *Server) GameList(w http.ResponseWriter, r *http.Request) { - resp, err := server.gm.ListGames() - if err != nil { - internalError(w, err.Error()) - return - } - json.NewEncoder(w).Encode(resp) -} - -func (server *Server) GameGet(w http.ResponseWriter, r *http.Request) { - var gameID string - err := router.Params(r, &gameID) - if err != nil { - badRequest(w, err.Error()) - return - } - resp, err := server.gm.GetGame(gameID) - if err != nil { - internalError(w, err.Error()) - return - } - json.NewEncoder(w).Encode(resp) -} - -func (server *Server) GameReplace(w http.ResponseWriter, r *http.Request) { - var gameID string - err := router.Params(r, &gameID) - if err != nil { - badRequest(w, err.Error()) - return - } - var game Game - err = json.NewDecoder(r.Body).Decode(&game) - if err != nil { - badRequest(w, err.Error()) - return - } - err = server.gm.ReplaceGame(gameID, game) - if err != nil { - internalError(w, err.Error()) - return - } - json.NewEncoder(w).Encode(game) -} - -func (server *Server) GameInsert(w http.ResponseWriter, r *http.Request) { - var gameID string - err := router.Params(r, &gameID) - if err != nil { - badRequest(w, err.Error()) - return - } - err = server.gm.CreateGame(gameID) - if err != nil { - internalError(w, err.Error()) - return - } - json.NewEncoder(w).Encode(Game{}) -} - -func notImpl(w http.ResponseWriter) { - errHandler(w, http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) -} - -func internalError(w http.ResponseWriter, message string) { - errHandler(w, http.StatusInternalServerError, message) -} - -func badRequest(w http.ResponseWriter, message string) { - errHandler(w, http.StatusBadRequest, message) -} - -func errHandler(w http.ResponseWriter, status int, message string) { - if message == storage.ErrNotFound.Error() { - status = http.StatusNotFound - } - w.WriteHeader(status) - json.NewEncoder(w).Encode(map[string]interface{}{ - "status": http.StatusText(status), - "statusCode": status, - "error": message, - }) -} diff --git a/src/server/server_test.go b/src/server/server_test.go deleted file mode 100644 index 593902a..0000000 --- a/src/server/server_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package main - -import ( - "net/http" - "net/http/httptest" - "path" - "strings" - "testing" -) - -func TestServerRouter(t *testing.T) { - gm := mockGameMaster(t) - server := NewServer(gm.config, gm) - if err := server.Routes(); err != nil { - t.Fatal(err) - } - - cases := map[string]struct { - method string - path string - }{ - "file server root": { - method: http.MethodGet, - path: path.Join(server.config.Server.File.Prefix), - }, - "api: games: get": { - method: http.MethodGet, - path: path.Join(server.config.Server.API.Prefix, "games"), - }, - "api: games: id: get": { - method: http.MethodGet, - path: path.Join(server.config.Server.API.Prefix, "games", "my-game-id"), - }, - "api: games: id: post": { - method: http.MethodPost, - path: path.Join(server.config.Server.API.Prefix, "games", "my-game-id"), - }, - "api: games: id: put": { - method: http.MethodPut, - path: path.Join(server.config.Server.API.Prefix, "games", "my-game-id"), - }, - } - - for name, d := range cases { - c := d - t.Run(name, func(t *testing.T) { - w := httptest.NewRecorder() - r := httptest.NewRequest(c.method, c.path, strings.NewReader("{}")) - server.ServeHTTP(w, r) - if w.Code == http.StatusNotFound && string(w.Body.Bytes()) == "404 page not found" { - t.Fatalf("not found: (%s) %s: (%v) %s", c.method, c.path, w.Code, w.Body.Bytes()) - } - }) - } -} - -func TestServerByMethod(t *testing.T) { - server := NewServer(Config{}, nil) - - gotGet := false - get := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotGet = true }) - gotPost := false - post := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotPost = true }) - - handler := server.ByMethod(map[string]http.HandlerFunc{ - http.MethodGet: get, - http.MethodPost: post, - }) - - handler(nil, &http.Request{Method: http.MethodGet}) - if !gotGet || gotPost { - t.Fatal(gotGet, gotPost) - } - gotGet = false - gotPost = false - - handler(nil, &http.Request{Method: http.MethodPost}) - if gotGet || !gotPost { - t.Fatal(gotGet, gotPost) - } -} diff --git a/src/server/lockmap.go b/src/server/storage/lockmap.go similarity index 97% rename from src/server/lockmap.go rename to src/server/storage/lockmap.go index c6d3a28..7070c84 100644 --- a/src/server/lockmap.go +++ b/src/server/storage/lockmap.go @@ -1,4 +1,4 @@ -package main +package storage import "sync" diff --git a/src/server/lockmap_test.go b/src/server/storage/lockmap_test.go similarity index 91% rename from src/server/lockmap_test.go rename to src/server/storage/lockmap_test.go index 6b5ccec..1ac54bc 100644 --- a/src/server/lockmap_test.go +++ b/src/server/storage/lockmap_test.go @@ -1,4 +1,4 @@ -package main +package storage import "testing" diff --git a/src/server/storage.go b/src/server/storage/storage.go similarity index 60% rename from src/server/storage.go rename to src/server/storage/storage.go index 7debb34..f2757b9 100644 --- a/src/server/storage.go +++ b/src/server/storage/storage.go @@ -1,7 +1,9 @@ -package main +package storage import ( "encoding/json" + "local/sandbox/blind-mans-poker/src/server/config" + "local/sandbox/blind-mans-poker/src/server/entity" "local/storage" ) @@ -10,18 +12,18 @@ const ( ) type Storage struct { - config Config + config config.Config db storage.DB } -func NewStorage(config Config) *Storage { +func NewStorage(config config.Config) *Storage { return &Storage{ config: config, - db: config.Game.db, + db: config.Game.DB, } } -func (storage Storage) ReplaceGame(id string, game Game) error { +func (storage Storage) ReplaceGame(id string, game entity.Game) error { b, err := json.Marshal(game) if err != nil { return err @@ -29,8 +31,8 @@ func (storage Storage) ReplaceGame(id string, game Game) error { return storage.db.Set(id, b, nsGames) } -func (storage Storage) GetGame(id string) (Game, error) { - var game Game +func (storage Storage) GetGame(id string) (entity.Game, error) { + var game entity.Game b, err := storage.db.Get(id, nsGames) if err != nil { @@ -42,7 +44,7 @@ func (storage Storage) GetGame(id string) (Game, error) { } func (storage Storage) CreateGame(id string) error { - b, err := json.Marshal(Game{}) + b, err := json.Marshal(entity.Game{}) if err != nil { return err }