impl server side
This commit is contained in:
52
src/server/config.go
Normal file
52
src/server/config.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/args"
|
||||||
|
"local/storage"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Game struct {
|
||||||
|
db storage.DB
|
||||||
|
}
|
||||||
|
Server struct {
|
||||||
|
Port int
|
||||||
|
File struct {
|
||||||
|
Root string
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
API struct {
|
||||||
|
Prefix string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConfig() Config {
|
||||||
|
as := args.NewArgSet()
|
||||||
|
as.Append(args.INT, "p", "port to serve", 4201)
|
||||||
|
as.Append(args.STRING, "dbaddr", "db path", "/tmp/blind.poker")
|
||||||
|
as.Append(args.STRING, "dbtype", "db type", "map")
|
||||||
|
as.Append(args.STRING, "root", "file server root", "../client")
|
||||||
|
as.Append(args.STRING, "root-http-prefix", "file server http prefix", "poker")
|
||||||
|
as.Append(args.STRING, "api-http-prefix", "api server http prefix", "api")
|
||||||
|
if err := as.Parse(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
db, err := storage.New(
|
||||||
|
storage.TypeFromString(as.GetString("dbtype")),
|
||||||
|
as.GetString("dbaddr"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := Config{}
|
||||||
|
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"), "/")
|
||||||
|
config.Server.API.Prefix = "/" + strings.TrimPrefix(as.GetString("api-http-prefix"), "/")
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
||||||
27
src/server/config_test.go
Normal file
27
src/server/config_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
7
src/server/errors.go
Normal file
7
src/server/errors.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
errGameExists = errors.New("cannot create game: already exists")
|
||||||
|
)
|
||||||
75
src/server/game.go
Normal file
75
src/server/game.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Game struct {
|
||||||
|
Pot Currency
|
||||||
|
Players [16]Player
|
||||||
|
}
|
||||||
|
|
||||||
|
type Player struct {
|
||||||
|
ID string
|
||||||
|
Name string
|
||||||
|
Card string
|
||||||
|
Balance Currency
|
||||||
|
}
|
||||||
|
|
||||||
|
type Currency int
|
||||||
|
|
||||||
|
func (game Game) GetPlayers() []Player {
|
||||||
|
players := []Player{}
|
||||||
|
for _, player := range game.Players {
|
||||||
|
if !player.Empty() {
|
||||||
|
players = append(players, player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return players
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Player) Empty() bool {
|
||||||
|
return p == (Player{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Currency) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(fmt.Sprintf(`$%v.%02d`, c/100, c%100))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Currency) UnmarshalJSON(b []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(b, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
re := regexp.MustCompile(`^\$([0-9]+(\.[0-9][0-9])?|\.[0-9]{2})$`)
|
||||||
|
if !re.MatchString(s) {
|
||||||
|
return fmt.Errorf("illegal currency format: %q", s)
|
||||||
|
}
|
||||||
|
s = strings.TrimPrefix(s, "$")
|
||||||
|
dollars := strings.Split(s, ".")[0]
|
||||||
|
cents := strings.TrimPrefix(s, dollars+".")
|
||||||
|
if !strings.HasPrefix(s, dollars+".") {
|
||||||
|
cents = "0"
|
||||||
|
}
|
||||||
|
if dollars == "" {
|
||||||
|
dollars = "0"
|
||||||
|
}
|
||||||
|
if cents == "00" {
|
||||||
|
cents = "0"
|
||||||
|
}
|
||||||
|
dI, err := strconv.Atoi(dollars)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cI, err := strconv.Atoi(cents)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c2 := Currency(dI*100 + cI)
|
||||||
|
*c = c2
|
||||||
|
return nil
|
||||||
|
}
|
||||||
121
src/server/game_test.go
Normal file
121
src/server/game_test.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCurrencyMarshal(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
input Currency
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
"zero": {
|
||||||
|
want: "$0.00",
|
||||||
|
},
|
||||||
|
"one cent": {
|
||||||
|
input: 1,
|
||||||
|
want: "$0.01",
|
||||||
|
},
|
||||||
|
"one dollar and one cent": {
|
||||||
|
input: 101,
|
||||||
|
want: "$1.01",
|
||||||
|
},
|
||||||
|
"one dollar and no cent": {
|
||||||
|
input: 100,
|
||||||
|
want: "$1.00",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, d := range cases {
|
||||||
|
c := d
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
b, err := json.Marshal(c.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
b = b[1 : len(b)-1]
|
||||||
|
if s := string(b); s != c.want {
|
||||||
|
t.Fatalf("given %v, want %q, got %q", c.input, c.want, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCurrencyUnmarshal(t *testing.T) {
|
||||||
|
cases := map[string]struct {
|
||||||
|
input string
|
||||||
|
want Currency
|
||||||
|
}{
|
||||||
|
"no dollar and no cent": {
|
||||||
|
input: "$0.00",
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
"omitted no dollar and no cent": {
|
||||||
|
input: "$.00",
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
"no dollar and omitted no cent": {
|
||||||
|
input: "$0",
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
"no dollar and one cent": {
|
||||||
|
input: "$0.01",
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
"omitted no dollar and one cent": {
|
||||||
|
input: "$.01",
|
||||||
|
want: 1,
|
||||||
|
},
|
||||||
|
"one dollar and one cent": {
|
||||||
|
input: "$1.01",
|
||||||
|
want: 101,
|
||||||
|
},
|
||||||
|
"one dollar and no cent": {
|
||||||
|
input: "$1.00",
|
||||||
|
want: 100,
|
||||||
|
},
|
||||||
|
"one dollar and omitted no cent": {
|
||||||
|
input: "$1",
|
||||||
|
want: 100,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, d := range cases {
|
||||||
|
c := d
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
var got Currency
|
||||||
|
err := json.Unmarshal([]byte(`"`+c.input+`"`), &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != c.want {
|
||||||
|
t.Fatalf("given %s, want %v, got %v", c.input, c.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlayerEmpty(t *testing.T) {
|
||||||
|
var p Player
|
||||||
|
if !p.Empty() {
|
||||||
|
t.Fatal(p)
|
||||||
|
}
|
||||||
|
p.ID = "id"
|
||||||
|
if p.Empty() {
|
||||||
|
t.Fatal(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGameGetPlayers(t *testing.T) {
|
||||||
|
var g Game
|
||||||
|
if players := g.GetPlayers(); len(players) != 0 {
|
||||||
|
t.Fatal(players)
|
||||||
|
}
|
||||||
|
g.Players[5].ID = "id"
|
||||||
|
if players := g.GetPlayers(); len(players) != 1 {
|
||||||
|
t.Fatal(players)
|
||||||
|
} else if players[0].ID != "id" {
|
||||||
|
t.Fatal(players[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/server/gamemaster.go
Normal file
37
src/server/gamemaster.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
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 (game *GameMaster) ListGames() ([]string, error) {
|
||||||
|
return game.storage.ListGames()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (game *GameMaster) GetGame(id string) (Game, error) {
|
||||||
|
game.locks.RLock(id)
|
||||||
|
defer game.locks.RUnlock(id)
|
||||||
|
|
||||||
|
return game.storage.GetGame(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (game *GameMaster) CreateGame(id string) error {
|
||||||
|
game.locks.Lock(id)
|
||||||
|
defer game.locks.Unlock(id)
|
||||||
|
|
||||||
|
if _, err := game.storage.GetGame(id); err == nil {
|
||||||
|
return errGameExists
|
||||||
|
}
|
||||||
|
|
||||||
|
return game.storage.CreateGame(id)
|
||||||
|
}
|
||||||
35
src/server/gamemaster_test.go
Normal file
35
src/server/gamemaster_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func mockGameMaster(t *testing.T) *GameMaster {
|
||||||
|
config := mockConfig(t)
|
||||||
|
storage := NewStorage(config)
|
||||||
|
return NewGameMaster(config, storage)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGameMasterGetCreateGet(t *testing.T) {
|
||||||
|
gm := mockGameMaster(t)
|
||||||
|
|
||||||
|
id := "game"
|
||||||
|
|
||||||
|
if games, err := gm.ListGames(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(games) != 0 {
|
||||||
|
t.Fatal(games)
|
||||||
|
}
|
||||||
|
if _, err := gm.GetGame(id); err == nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := gm.CreateGame(id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err := gm.GetGame(id); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if games, err := gm.ListGames(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if len(games) != 1 {
|
||||||
|
t.Fatal(games)
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/server/lockmap.go
Normal file
38
src/server/lockmap.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type RWLockMap struct {
|
||||||
|
m *sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRWLockMap() *RWLockMap {
|
||||||
|
return &RWLockMap{
|
||||||
|
m: &sync.Map{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwlm *RWLockMap) Lock(k string) {
|
||||||
|
rwlm.getLock(k).Lock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwlm *RWLockMap) RLock(k string) {
|
||||||
|
rwlm.getLock(k).RLock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwlm *RWLockMap) RUnlock(k string) {
|
||||||
|
rwlm.getLock(k).RUnlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwlm *RWLockMap) Unlock(k string) {
|
||||||
|
rwlm.getLock(k).Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rwlm *RWLockMap) getLock(k string) *sync.RWMutex {
|
||||||
|
v, ok := rwlm.m.Load(k)
|
||||||
|
if !ok {
|
||||||
|
v = &sync.RWMutex{}
|
||||||
|
rwlm.m.Store(k, v)
|
||||||
|
}
|
||||||
|
return v.(*sync.RWMutex)
|
||||||
|
}
|
||||||
14
src/server/lockmap_test.go
Normal file
14
src/server/lockmap_test.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLockMap(t *testing.T) {
|
||||||
|
rwlm := NewRWLockMap()
|
||||||
|
k1 := "k1"
|
||||||
|
k2 := "k2"
|
||||||
|
|
||||||
|
rwlm.Lock(k1)
|
||||||
|
rwlm.RLock(k2)
|
||||||
|
rwlm.Unlock(k1)
|
||||||
|
rwlm.RUnlock(k2)
|
||||||
|
}
|
||||||
21
src/server/main.go
Normal file
21
src/server/main.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := NewConfig()
|
||||||
|
storage := NewStorage(config)
|
||||||
|
gm := NewGameMaster(config, storage)
|
||||||
|
server := NewServer(config, gm)
|
||||||
|
if err := server.Routes(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
log.Println(config)
|
||||||
|
if err := http.ListenAndServe(fmt.Sprintf(":%d", config.Server.Port), server); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
118
src/server/server.go
Normal file
118
src/server/server.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"local/router"
|
||||||
|
"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/%s", server.config.Server.API.Prefix, router.Wildcard): map[string]http.HandlerFunc{
|
||||||
|
http.MethodGet: server.GameGet,
|
||||||
|
http.MethodPost: server.GameInsert,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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) 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) 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) {
|
||||||
|
w.WriteHeader(status)
|
||||||
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"status": http.StatusText(status),
|
||||||
|
"statusCode": status,
|
||||||
|
"error": message,
|
||||||
|
})
|
||||||
|
}
|
||||||
73
src/server/server_test.go
Normal file
73
src/server/server_test.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
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", "my-game-id"),
|
||||||
|
},
|
||||||
|
"api: games: post": {
|
||||||
|
method: http.MethodPost,
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/server/storage.go
Normal file
47
src/server/storage.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"local/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nsGames = "games"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage struct {
|
||||||
|
config Config
|
||||||
|
db storage.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStorage(config Config) *Storage {
|
||||||
|
return &Storage{
|
||||||
|
config: config,
|
||||||
|
db: config.Game.db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (storage Storage) GetGame(id string) (Game, error) {
|
||||||
|
var game Game
|
||||||
|
|
||||||
|
b, err := storage.db.Get(id, nsGames)
|
||||||
|
if err != nil {
|
||||||
|
return game, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(b, &game)
|
||||||
|
return game, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (storage Storage) CreateGame(id string) error {
|
||||||
|
b, err := json.Marshal(Game{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return storage.db.Set(id, b, nsGames)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (storage Storage) ListGames() ([]string, error) {
|
||||||
|
return storage.db.List([]string{nsGames})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user