reorg
parent
cb42bdc8d0
commit
4bf83b3e40
|
|
@ -2,10 +2,10 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"local/sandbox/blind-mans-poker/src/server/config"
|
"local/sandbox/cards/src/config"
|
||||||
"local/sandbox/blind-mans-poker/src/server/game"
|
"local/sandbox/cards/src/game"
|
||||||
"local/sandbox/blind-mans-poker/src/server/server"
|
"local/sandbox/cards/src/server"
|
||||||
"local/sandbox/blind-mans-poker/src/server/storage"
|
"local/sandbox/cards/src/storage"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
@ -1,20 +1,27 @@
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"local/sandbox/blind-mans-poker/src/server/config"
|
"local/sandbox/cards/src/config"
|
||||||
"local/sandbox/blind-mans-poker/src/server/consts"
|
"local/sandbox/cards/src/consts"
|
||||||
"local/sandbox/blind-mans-poker/src/server/entity"
|
"local/sandbox/cards/src/entity"
|
||||||
"local/sandbox/blind-mans-poker/src/server/storage"
|
"local/sandbox/cards/src/storage"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Master struct {
|
type Master struct {
|
||||||
config config.Config
|
config config.Config
|
||||||
storage *storage.Storage
|
storage Storage
|
||||||
locks *storage.RWLockMap
|
locks *storage.RWLockMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMaster(config config.Config, s *storage.Storage) *Master {
|
type Storage interface {
|
||||||
|
CreateGame(string) error
|
||||||
|
GetGame(string) (entity.Game, error)
|
||||||
|
ListGames() ([]string, error)
|
||||||
|
ReplaceGame(string, entity.Game) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMaster(config config.Config, s Storage) *Master {
|
||||||
return &Master{
|
return &Master{
|
||||||
config: config,
|
config: config,
|
||||||
storage: s,
|
storage: s,
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"local/sandbox/blind-mans-poker/src/server/entity"
|
"local/sandbox/cards/src/entity"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package game
|
||||||
|
|
||||||
|
type Rule interface {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"local/router"
|
||||||
|
"local/sandbox/cards/src/config"
|
||||||
|
"local/sandbox/cards/src/entity"
|
||||||
|
"local/sandbox/cards/src/game"
|
||||||
|
"local/storage"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
config config.Config
|
||||||
|
gm *game.Master
|
||||||
|
*router.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(config config.Config, gm *game.Master) *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 entity.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(entity.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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/sandbox/cards/src/config"
|
||||||
|
"local/sandbox/cards/src/game"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServerRouter(t *testing.T) {
|
||||||
|
config := config.NewTestConfig(t)
|
||||||
|
gm := game.NewTestMaster(t)
|
||||||
|
server := NewServer(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.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"local/sandbox/blind-mans-poker/src/server/config"
|
"local/sandbox/cards/src/config"
|
||||||
"local/sandbox/blind-mans-poker/src/server/entity"
|
"local/sandbox/cards/src/entity"
|
||||||
"local/storage"
|
"local/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
Loading…
Reference in New Issue