From d7c4f28f3a4fe491fc1132bc0d1703f59a4207de Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 9 May 2024 22:29:48 +0200 Subject: [PATCH] Fix endpoint for getting lobby id from passphrase Had to add several helpers (e.g. passphrase ones) to make the endpoint for getting lobby id work. Moved all handler functions into handler package. Added test for getting lobby from passphrase. --- handler/handler.go | 108 ++++++++++++++++++++++++++++++++++++--- handler/handler_test.go | 57 +++++++++++++++++++++ main.go | 104 ++----------------------------------- utils/clean-words.go | 1 - utils/passphrase.go | 28 ++++++++++ utils/passphrase_test.go | 15 ++++++ 6 files changed, 206 insertions(+), 107 deletions(-) create mode 100644 handler/handler_test.go diff --git a/handler/handler.go b/handler/handler.go index f3cf24e..6615f86 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -1,25 +1,49 @@ package handler import ( + "log" "mchess_server/api" + "mchess_server/chess" lobbies "mchess_server/lobby_registry" + "mchess_server/usher" "mchess_server/utils" "net/http" + "sync" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) -func GetLobbyFromPassphraseHandler(c *gin.Context) { - reqPassphrase := api.Passphrase{} +var mut sync.Mutex - err := c.ShouldBindJSON(&reqPassphrase) - if err != nil || reqPassphrase.Value == nil || *reqPassphrase.Value == "" { - c.IndentedJSON(http.StatusNotFound, reqPassphrase) +func HostPrivateGameHandler(c *gin.Context) { + player := chess.NewPlayer(uuid.New()) + u := usher.GetUsher() + + mut.Lock() + defer mut.Unlock() + lobby := u.CreateNewPrivateLobby(player) + u.AddPlayerToLobbyAndStartGameIfFull(player, lobby) + + passphrase := lobby.Passphrase.String() + info := api.PlayerInfo{ + PlayerID: &player.Uuid, + LobbyID: &lobby.Uuid, + Passphrase: &passphrase, + } + c.Header("Access-Control-Allow-Origin", "*") + c.IndentedJSON(http.StatusOK, info) +} + +func GetLobbyForPassphraseHandler(c *gin.Context) { + reqPassphrase := c.Param("phrase") + if reqPassphrase == "" { + c.IndentedJSON(http.StatusBadRequest, reqPassphrase) return } - lobby := lobbies.GetLobbyRegistry().GetLobbyByPassphrase( - utils.NewPassphraseFromString(*reqPassphrase.Value)) + passPhraseWithSpaces := utils.ConvertToPassphraseWithSpaces(reqPassphrase) + lobby := lobbies.GetLobbyRegistry().GetLobbyByPassphrase(passPhraseWithSpaces) if lobby == nil { c.IndentedJSON(http.StatusNotFound, reqPassphrase) @@ -32,3 +56,73 @@ func GetLobbyFromPassphraseHandler(c *gin.Context) { c.IndentedJSON(http.StatusOK, lobbyInfo) } + +func RegisterForRandomGame(c *gin.Context) { + player := chess.NewPlayer(uuid.New()) + usher := usher.GetUsher() + + mut.Lock() + defer mut.Unlock() + lobby := usher.WelcomeNewPlayer(player) + usher.AddPlayerToLobbyAndStartGameIfFull(player, lobby) + + info := api.PlayerInfo{ + PlayerID: &player.Uuid, + LobbyID: &lobby.Uuid, + } + log.Println("responding with info ", info) + + c.Header("Access-Control-Allow-Origin", "*") + c.IndentedJSON(http.StatusOK, info) +} + +func JoinPrivateGame(c *gin.Context) { + req := api.PlayerInfo{} + log.Println(c.Request.Body) + err := c.ShouldBindJSON(&req) + if err != nil || req.Passphrase == nil || *req.Passphrase == "" { + c.IndentedJSON(http.StatusNotFound, req) + } + + u := usher.GetUsher() + + if req.Passphrase != nil && + *req.Passphrase != "" && + req.PlayerID != nil && + req.LobbyID != nil { //is reconnect + lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase)) + _, found := lobby.GetPlayerByUUID(*req.PlayerID) + if found { + c.IndentedJSON( + http.StatusOK, + api.PlayerInfo{ + PlayerID: req.PlayerID, + LobbyID: req.LobbyID, + Passphrase: req.Passphrase, + }) + return + } else { + c.IndentedJSON(http.StatusNotFound, req) + } + } + + player := chess.NewPlayer(uuid.New()) + + mut.Lock() + defer mut.Unlock() + lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase)) + if lobby != nil { + u.AddPlayerToLobbyAndStartGameIfFull(player, lobby) + } else { + c.IndentedJSON(http.StatusNotFound, req) + return + } + + info := api.PlayerInfo{ + PlayerID: &player.Uuid, + LobbyID: &lobby.Uuid, + Passphrase: req.Passphrase, + } + c.Header("Access-Control-Allow-Origin", "*") + c.IndentedJSON(http.StatusOK, info) +} diff --git a/handler/handler_test.go b/handler/handler_test.go new file mode 100644 index 0000000..8e69445 --- /dev/null +++ b/handler/handler_test.go @@ -0,0 +1,57 @@ +package handler + +import ( + "encoding/json" + "mchess_server/api" + "mchess_server/utils" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func Test_GetLobbyFromPassphraseHandler(t *testing.T) { + var passphraseURLParameter string + var hostedLobbyId uuid.UUID + + t.Run("host a lobby", func(t *testing.T) { + r1 := httptest.NewRecorder() + ctx1, e1 := gin.CreateTestContext(r1) + e1.GET("/api/hostPrivate", HostPrivateGameHandler) + hostGameRequest, _ := http.NewRequest("GET", "/api/hostPrivate", nil) + ctx1.Request = hostGameRequest + + e1.ServeHTTP(r1, hostGameRequest) + + playerInfo := api.PlayerInfo{} + err := json.Unmarshal(r1.Body.Bytes(), &playerInfo) + assert.NoError(t, err) + + receivedPhrase := *playerInfo.Passphrase + hostedLobbyId = *playerInfo.LobbyID + + passphrase := utils.NewPassphraseFromString(receivedPhrase) + passphraseURLParameter = passphrase.AsURLParam() + }) + + t.Run("see if the lobby can be fetched by using the passphrase", func(t *testing.T) { + r2 := httptest.NewRecorder() + ctx2, engine := gin.CreateTestContext(r2) + engine.GET("/api/getLobbyForPassphrase/:phrase", GetLobbyForPassphraseHandler) + + url := "/api/getLobbyForPassphrase/" + passphraseURLParameter + getLobbyRequest, _ := http.NewRequest("GET", url, nil) + ctx2.Request = getLobbyRequest + + engine.ServeHTTP(r2, getLobbyRequest) + + lobbyInfo := api.LobbyInfo{} + err := json.Unmarshal(r2.Body.Bytes(), &lobbyInfo) + assert.NoError(t, err) + + assert.Equal(t, hostedLobbyId, *lobbyInfo.ID) + }) +} diff --git a/main.go b/main.go index a2432ef..5c7eb8f 100644 --- a/main.go +++ b/main.go @@ -7,22 +7,16 @@ import ( "fmt" "log" "mchess_server/api" - "mchess_server/chess" + "mchess_server/handler" lobbies "mchess_server/lobby_registry" - "mchess_server/usher" - "mchess_server/utils" - "net/http" - "sync" "github.com/gin-gonic/gin" - "github.com/google/uuid" "nhooyr.io/websocket" ) var cert_path = "/etc/letsencrypt/live/chess.sw-gross.de/" var cert_file = cert_path + "fullchain.pem" var key_file = cert_path + "privkey.pem" -var mut sync.Mutex func main() { var debugMode bool @@ -35,10 +29,11 @@ func main() { } router := gin.Default() - router.GET("/api/random", registerForRandomGame) - router.GET("/api/hostPrivate", hostPrivateGame) - router.POST("/api/joinPrivate", joinPrivateGame) + router.GET("/api/random", handler.RegisterForRandomGame) + router.GET("/api/hostPrivate", handler.HostPrivateGameHandler) + router.POST("/api/joinPrivate", handler.JoinPrivateGame) router.GET("/api/ws", registerWebSocketConnection) + router.GET("/api/getLobbyForPassphrase/:phrase", handler.GetLobbyForPassphraseHandler) if debugMode { log.Println("Starting service WITHOUT TLS") @@ -51,95 +46,6 @@ func main() { } } -func registerForRandomGame(c *gin.Context) { - player := chess.NewPlayer(uuid.New()) - usher := usher.GetUsher() - - mut.Lock() - defer mut.Unlock() - lobby := usher.WelcomeNewPlayer(player) - usher.AddPlayerToLobbyAndStartGameIfFull(player, lobby) - - info := api.PlayerInfo{ - PlayerID: &player.Uuid, - LobbyID: &lobby.Uuid, - } - log.Println("responding with info ", info) - - c.Header("Access-Control-Allow-Origin", "*") - c.IndentedJSON(http.StatusOK, info) -} - -func hostPrivateGame(c *gin.Context) { - player := chess.NewPlayer(uuid.New()) - u := usher.GetUsher() - - mut.Lock() - defer mut.Unlock() - lobby := u.CreateNewPrivateLobby(player) - u.AddPlayerToLobbyAndStartGameIfFull(player, lobby) - - passphrase := lobby.Passphrase.String() - info := api.PlayerInfo{ - PlayerID: &player.Uuid, - LobbyID: &lobby.Uuid, - Passphrase: &passphrase, - } - c.Header("Access-Control-Allow-Origin", "*") - c.IndentedJSON(http.StatusOK, info) -} - -func joinPrivateGame(c *gin.Context) { - req := api.PlayerInfo{} - log.Println(c.Request.Body) - err := c.ShouldBindJSON(&req) - if err != nil || req.Passphrase == nil || *req.Passphrase == "" { - c.IndentedJSON(http.StatusNotFound, req) - } - - u := usher.GetUsher() - - if req.Passphrase != nil && - *req.Passphrase != "" && - req.PlayerID != nil && - req.LobbyID != nil { //is reconnect - lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase)) - _, found := lobby.GetPlayerByUUID(*req.PlayerID) - if found { - c.IndentedJSON( - http.StatusOK, - api.PlayerInfo{ - PlayerID: req.PlayerID, - LobbyID: req.LobbyID, - Passphrase: req.Passphrase, - }) - return - } else { - c.IndentedJSON(http.StatusNotFound, req) - } - } - - player := chess.NewPlayer(uuid.New()) - - mut.Lock() - defer mut.Unlock() - lobby := u.FindExistingPrivateLobby(utils.Passphrase(*req.Passphrase)) - if lobby != nil { - u.AddPlayerToLobbyAndStartGameIfFull(player, lobby) - } else { - c.IndentedJSON(http.StatusNotFound, req) - return - } - - info := api.PlayerInfo{ - PlayerID: &player.Uuid, - LobbyID: &lobby.Uuid, - Passphrase: req.Passphrase, - } - c.Header("Access-Control-Allow-Origin", "*") - c.IndentedJSON(http.StatusOK, info) -} - func registerWebSocketConnection(c *gin.Context) { webSocketConn, err := websocket.Accept(c.Writer, c.Request, &websocket.AcceptOptions{OriginPatterns: []string{"chess.sw-gross.de", "localhost:*"}}) if err != nil { diff --git a/utils/clean-words.go b/utils/clean-words.go index 6dda176..2e32f0f 100644 --- a/utils/clean-words.go +++ b/utils/clean-words.go @@ -28006,7 +28006,6 @@ var CleanWords = []string{ "joystick", "joysticks", "jpeg", - "jpn", "juan", "juana", "juanita", diff --git a/utils/passphrase.go b/utils/passphrase.go index 1cbf6d9..7311667 100644 --- a/utils/passphrase.go +++ b/utils/passphrase.go @@ -2,6 +2,7 @@ package utils import ( "strings" + "unicode" ) type Passphrase string @@ -24,6 +25,33 @@ func NewPassphraseFromString(s string) Passphrase { return Passphrase(s) } +func (p Passphrase) AsURLParam() string { + var result string + phraseAsString := p.String() + + segments := strings.Split(phraseAsString, " ") + + for _, segment := range segments { + runes := []rune(segment) + runes[0] = unicode.ToUpper(runes[0]) + result += string(runes) + } + + return result +} + +func ConvertToPassphraseWithSpaces(s string) Passphrase { + result := "" + for _, rune := range s { + if unicode.IsUpper(rune) { + result += " " + } + result += strings.ToLower(string(rune)) + } + + return NewPassphraseFromString(strings.Trim(result, " ")) +} + func (p Passphrase) String() string { return string(p) } diff --git a/utils/passphrase_test.go b/utils/passphrase_test.go index 5876bfa..9af9048 100644 --- a/utils/passphrase_test.go +++ b/utils/passphrase_test.go @@ -19,3 +19,18 @@ func Test_onlyUniqueWords(t *testing.T) { assert.Equal(t, numberOfCleanWords, len(wordsDistribution)) } + +func Test_Passphrase_AsURLParam(t *testing.T) { + phrase := NewPassphraseFromString("this is a Test phrase") + + asParams := phrase.AsURLParam() + + assert.Equal(t, "ThisIsATestPhrase", asParams) +} + +func Test_Passphrase_ConvertToPassphraseWithSpaces(t *testing.T) { + fromURL := "ThisIsATestPhraseWithManyWords" + phrase := ConvertToPassphraseWithSpaces(fromURL) + + assert.Equal(t, NewPassphraseFromString("this is a test phrase with many words"), phrase) +}