From 4aae0896aaaf950f0ca142c2aaf1c94adae2fe54 Mon Sep 17 00:00:00 2001 From: MaxJa4 <74194322+MaxJa4@users.noreply.github.com> Date: Sun, 21 Jan 2024 17:24:29 +0100 Subject: [PATCH] Optimizations. User-Role handling in templates and routes. --- auth.go | 24 +++++--- controllers/cache_controller.go | 27 +++++--- controllers/player_controller.go | 50 +++++++++------ controllers/user_controller.go | 19 ++++++ internal/cache/cache.go | 28 +++++++++ main.go | 71 ++++++++++++---------- pages.go | 9 +-- templates/components/bottom_controls.html | 2 +- templates/components/home_clan_bar.html | 20 +++--- templates/components/home_player_list.html | 10 +-- templates/components/opp_clan_bar.html | 20 +++--- templates/components/opp_player_list.html | 10 +-- templates/player_list_item.html | 44 ++++++++------ utils/globals.go | 6 ++ 14 files changed, 221 insertions(+), 119 deletions(-) diff --git a/auth.go b/auth.go index 6a62525..dc37b9d 100644 --- a/auth.go +++ b/auth.go @@ -46,7 +46,7 @@ func hashPassword(password string) (string, error) { return string(hashedPassword), nil } -func AuthRequired() gin.HandlerFunc { +func ReaderAuthRequired() gin.HandlerFunc { return func(c *gin.Context) { auth, okAuth := session.GetAuthenticated(c) username, okUser := session.GetUsername(c) @@ -60,6 +60,20 @@ func AuthRequired() gin.HandlerFunc { } } +func AuthorAuthRequired() gin.HandlerFunc { + return func(c *gin.Context) { + auth, okAuth := session.GetAuthenticated(c) + username, okUser := session.GetUsername(c) + + if !okAuth || !okUser || !auth || !controllers.IsUserEnabled(username) || controllers.GetUserRole(username) == models.ReaderRole { + redirectToLogin(c) + return + } + + c.Next() + } +} + func AdminAuthRequired() gin.HandlerFunc { return func(c *gin.Context) { auth, okAuth := session.GetAuthenticated(c) @@ -74,14 +88,6 @@ func AdminAuthRequired() gin.HandlerFunc { } } -func isUserAdmin(c *gin.Context) bool { - username, ok := session.GetUsername(c) - if !ok { - return false - } - return controllers.IsUserAdmin(username) -} - func redirectToLogin(c *gin.Context) { if err := session.InvalidateSession(c); err != nil { log.Fatal(err) diff --git a/controllers/cache_controller.go b/controllers/cache_controller.go index 1fffd34..b6a5ed6 100644 --- a/controllers/cache_controller.go +++ b/controllers/cache_controller.go @@ -20,12 +20,17 @@ type UpdateCacheInput struct { Score float32 `json:"score" gorm:"default:-1.0"` } -// GetCacheByPlayerID GET /cache/:player_id?game_tag=TAG +// GetCacheByPlayerID GET /cache/:player_id func GetCacheByPlayerID(c *gin.Context) { playerId := utils.StringToUint(c.Param("player_id")) - gameTag := c.Request.URL.Query().Get("game_tag") - val, err := cache.GetScore(playerId, gameTag) + game, err := GetActiveGame(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "No active game available!"}) + return + } + + val, err := cache.GetScore(playerId, game.Tag) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"}) } else { @@ -91,11 +96,11 @@ func DeleteAllCaches(c *gin.Context) { } } -// GetScoreByPlayerID GET /score/:player_id?game_tag=TAG +// GetScoreByPlayerID GET /score/:player_id func GetScoreByPlayerID(c *gin.Context) { var player models.Player - var gameTag = c.Request.URL.Query().Get("game_tag") var playerId = utils.StringToUint(c.Param("player_id")) + if err := models.DB. Model(&models.Player{}). Where("id = ?", playerId). @@ -105,11 +110,17 @@ func GetScoreByPlayerID(c *gin.Context) { return } - score, err := cache.GetScore(player.ID, gameTag) + game, err := GetActiveGame(c) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "No active game available!"}) + return + } + + score, err := cache.GetScore(player.ID, game.Tag) if err != nil || score == -1 { - score = utils.CalcPlayerScore(player.Name, gameTag) + score = utils.CalcPlayerScore(player.Name, game.Tag) if score == score && score != -1 { // not NaN - if err := cache.SetScore(player.ID, gameTag, score); err != nil { + if err := cache.SetScore(player.ID, game.Tag, score); err != nil { log.Fatal(err) } } diff --git a/controllers/player_controller.go b/controllers/player_controller.go index dc41a0c..140cea7 100644 --- a/controllers/player_controller.go +++ b/controllers/player_controller.go @@ -7,10 +7,10 @@ import ( "github.com/gin-gonic/gin" "gorm.io/gorm" "gorm.io/gorm/clause" + "html/template" "internal/cache" "log" "net/http" - "os" ) type AddPlayerInput struct { @@ -44,14 +44,6 @@ func GetPlayersByClanHTML(c *gin.Context) { return } - file, err := os.ReadFile("./templates/player_list_item.html") - if err != nil { - c.String(http.StatusBadRequest, "") - log.Fatal(err) - return - } - playerItem := string(file) - game, err := GetActiveGame(c) if err != nil { c.String(http.StatusBadRequest, "") @@ -59,19 +51,37 @@ func GetPlayersByClanHTML(c *gin.Context) { return } - var htmlOptions string - for _, player := range players { - var score string - if val, err := cache.GetScore(player.ID, game.Tag); err != nil || val == -1.0 { - score = "" - } else { - score = fmt.Sprintf("%.2f", val) - } - htmlOptions += fmt.Sprintf(playerItem, player.Name, score, utils.UintToString(player.ID)+"?game_tag="+game.Tag, player.ID, player.ID) + scores, err := cache.GetScores(players, game.Tag) + if err != nil { + c.String(http.StatusBadRequest, "") + log.Fatal(err) } - c.Header("Content-Type", "text/html") - c.String(http.StatusOK, htmlOptions) + userRole := GetUserRoleByCtx(c) + + var data []map[string]interface{} + for i, player := range players { + + score := scores[i] + var scoreStr string + if score == -1.0 { + scoreStr = "" + } else { + scoreStr = fmt.Sprintf("%.2f", score) + } + + data = append(data, map[string]interface{}{ + "PlayerName": player.Name, + "Score": template.HTML(scoreStr), + "PlayerID": player.ID, + "UserRole": userRole, + }) + } + + err = utils.PlayerItemTemplate.Execute(c.Writer, data) + if err != nil { + log.Fatal(err) + } } // AddPlayer POST /player diff --git a/controllers/user_controller.go b/controllers/user_controller.go index 657dd56..0b086c9 100644 --- a/controllers/user_controller.go +++ b/controllers/user_controller.go @@ -2,7 +2,9 @@ package controllers import ( "InfantrySkillCalculator/models" + "github.com/gin-gonic/gin" "log" + "session" ) func CreateUser(username string, hashedPassword string, enabled bool, usedCode string) { @@ -56,6 +58,23 @@ func IsUserEnabled(username string) bool { return user.Enabled } +func GetUserRole(username string) models.Role { + var user models.User + err := models.DB.Where("username = ?", username).First(&user).Error + if err != nil { + log.Fatal(err) + } + return user.UserRole +} + +func GetUserRoleByCtx(c *gin.Context) models.Role { + username, ok := session.GetUsername(c) + if !ok { + return models.ReaderRole + } + return GetUserRole(username) +} + func IsUserAdmin(username string) bool { var user models.User err := models.DB.Where("username = ?", username).First(&user).Error diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 6db81bd..6a45bd2 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -41,6 +41,34 @@ func GetScore(playerId uint, gameTag string) (float32, error) { } } +func GetScores(players []models.Player, gameTag string) ([]float32, error) { + vals, err := models.Cache.Pipelined(ctx, func(pipe redis.Pipeliner) error { + for _, p := range players { + key := GetPlayerCacheKey(p.ID, gameTag) + pipe.Get(ctx, key) + } + return nil + }) + if err != nil && !errors.Is(err, redis.Nil) { + return nil, err + } + + var scores []float32 + for _, val := range vals { + score, err := val.(*redis.StringCmd).Float32() + + if errors.Is(err, redis.Nil) { // cache miss + score = -1 + err = nil + } else if err != nil { // cache error + score = -1 + } + + scores = append(scores, score) + } + return scores, nil +} + func DeleteScore(playerId uint, gameTag string) error { key := GetPlayerCacheKey(playerId, gameTag) return models.Cache.Del(ctx, key).Err() diff --git a/main.go b/main.go index 02e275f..716ab3c 100644 --- a/main.go +++ b/main.go @@ -12,13 +12,9 @@ import ( "os" ) -var mainPageTemplates *template.Template -var loginPageTemplates *template.Template -var registerPageTemplates *template.Template - func init() { var err error - mainPageTemplates, err = template.ParseFiles( + utils.MainPageTemplates, err = template.ParseFiles( "./templates/index.html", "./templates/components/home_clan_bar.html", "./templates/components/opp_clan_bar.html", @@ -38,7 +34,7 @@ func init() { log.Fatal(err) } - loginPageTemplates, err = template.ParseFiles( + utils.LoginPageTemplates, err = template.ParseFiles( "./templates/login.html", "./templates/components/header.html", ) @@ -46,7 +42,7 @@ func init() { log.Fatal(err) } - registerPageTemplates, err = template.ParseFiles( + utils.RegisterPageTemplates, err = template.ParseFiles( "./templates/register.html", "./templates/components/header.html", ) @@ -54,6 +50,13 @@ func init() { log.Fatal(err) } + utils.PlayerItemTemplate, err = template.ParseFiles( + "./templates/player_list_item.html", + ) + if err != nil { + log.Fatal(err) + } + controllers.LoadMetrics() } @@ -68,8 +71,10 @@ func main() { log.Fatal(err) } router.LoadHTMLGlob("templates/**/*") - protected := router.Group("/") - protected.Use(AuthRequired()) + reader := router.Group("/") + reader.Use(ReaderAuthRequired()) + author := router.Group("/") + author.Use(AuthorAuthRequired()) admin := router.Group("/admin") admin.Use(AdminAuthRequired()) @@ -89,7 +94,11 @@ func main() { gin.LoggerWithWriter(utils.GinWriter, "/static"), gin.Recovery(), ) - protected.Use( + reader.Use( + gin.LoggerWithWriter(utils.GinWriter), + gin.Recovery(), + ) + author.Use( gin.LoggerWithWriter(utils.GinWriter), gin.Recovery(), ) @@ -106,33 +115,33 @@ func main() { router.GET("/register", registerPage) router.POST("/register", registerPost) - protected.GET("/", mainPage) + reader.GET("/", mainPage) - protected.GET("/clans", controllers.GetAllClans) - protected.GET("/clans_html", controllers.GetAllClansHTML) - protected.GET("/clan/:id", controllers.GetClanByID) - protected.POST("/clan", controllers.AddClan) - protected.PATCH("/clan/:id", controllers.UpdateClanByID) - protected.DELETE("/clan/:id", controllers.DeleteClanByID) + reader.GET("/clans", controllers.GetAllClans) + reader.GET("/clans_html", controllers.GetAllClansHTML) + reader.GET("/clan/:id", controllers.GetClanByID) + author.POST("/clan", controllers.AddClan) + author.PATCH("/clan/:id", controllers.UpdateClanByID) + author.DELETE("/clan/:id", controllers.DeleteClanByID) - protected.GET("/players", controllers.GetAllPlayers) - protected.GET("/players_html", controllers.GetPlayersByClanHTML) - protected.GET("/player/:id", controllers.GetPlayerByID) - protected.GET("/playerid/:name", controllers.GetPlayerIDByName) - protected.POST("/player", controllers.AddPlayer) - protected.PATCH("/player/:id", controllers.UpdatePlayerByID) - protected.DELETE("/player/:id", controllers.DeletePlayerByID) + reader.GET("/players", controllers.GetAllPlayers) + reader.GET("/players_html", controllers.GetPlayersByClanHTML) + reader.GET("/player/:id", controllers.GetPlayerByID) + reader.GET("/playerid/:name", controllers.GetPlayerIDByName) + author.POST("/player", controllers.AddPlayer) + author.PATCH("/player/:id", controllers.UpdatePlayerByID) + author.DELETE("/player/:id", controllers.DeletePlayerByID) - protected.GET("/cache/:player_id", controllers.GetCacheByPlayerID) + reader.GET("/cache/:player_id", controllers.GetCacheByPlayerID) - protected.GET("/score/:player_id", controllers.GetScoreByPlayerID) - protected.POST("/score/:player_name", controllers.GetScoreByPlayerName) + reader.GET("/score/:player_id", controllers.GetScoreByPlayerID) + reader.POST("/score/:player_name", controllers.GetScoreByPlayerName) - protected.GET("/game", controllers.GetGames) - protected.GET("/game_html", controllers.GetGamesHTML) + reader.GET("/game", controllers.GetGames) + reader.GET("/game_html", controllers.GetGamesHTML) - protected.GET("/settings", controllers.GetSettings) - protected.PATCH("/settings", controllers.UpdateSettings) + reader.GET("/settings", controllers.GetSettings) + reader.PATCH("/settings", controllers.UpdateSettings) admin.DELETE("/clear_cache", controllers.DeleteAllCaches) admin.DELETE("/purge_players", controllers.DeleteAllPlayers) diff --git a/pages.go b/pages.go index 60e10cc..3022c7c 100644 --- a/pages.go +++ b/pages.go @@ -2,6 +2,7 @@ package main import ( "InfantrySkillCalculator/controllers" + "InfantrySkillCalculator/utils" "github.com/gin-gonic/gin" "log" "net/http" @@ -10,10 +11,10 @@ import ( func mainPage(c *gin.Context) { data := map[string]interface{}{ - "isAdmin": isUserAdmin(c), + "UserRole": controllers.GetUserRoleByCtx(c), } - err := mainPageTemplates.Execute(c.Writer, data) + err := utils.MainPageTemplates.Execute(c.Writer, data) if err != nil { log.Fatal(err) } @@ -25,7 +26,7 @@ func loginPage(c *gin.Context) { return } - err := loginPageTemplates.Execute(c.Writer, nil) + err := utils.LoginPageTemplates.Execute(c.Writer, nil) if err != nil { log.Fatal(err) } @@ -64,7 +65,7 @@ func registerPage(c *gin.Context) { return } - err := registerPageTemplates.Execute(c.Writer, nil) + err := utils.RegisterPageTemplates.Execute(c.Writer, nil) if err != nil { log.Fatal(err) } diff --git a/templates/components/bottom_controls.html b/templates/components/bottom_controls.html index 1786ef0..6d2ee60 100644 --- a/templates/components/bottom_controls.html +++ b/templates/components/bottom_controls.html @@ -9,7 +9,7 @@ - {{ if .isAdmin }} + {{ if (eq .UserRole "ADMIN") }} diff --git a/templates/components/home_player_list.html b/templates/components/home_player_list.html index c5e5071..6cf34c0 100644 --- a/templates/components/home_player_list.html +++ b/templates/components/home_player_list.html @@ -15,10 +15,12 @@ -
- + {{ if not (eq .UserRole "READER") }} +
+ + {{ end }}
diff --git a/templates/components/opp_clan_bar.html b/templates/components/opp_clan_bar.html index ae36e4e..5a58096 100644 --- a/templates/components/opp_clan_bar.html +++ b/templates/components/opp_clan_bar.html @@ -9,15 +9,17 @@ - - - + {{ if not (eq .UserRole "READER") }} + + + + {{ end }}
diff --git a/templates/components/opp_player_list.html b/templates/components/opp_player_list.html index 18f1748..d28919d 100644 --- a/templates/components/opp_player_list.html +++ b/templates/components/opp_player_list.html @@ -15,10 +15,12 @@ -
- + {{ if not (eq .UserRole "READER") }} +
+ + {{ end }}
diff --git a/templates/player_list_item.html b/templates/player_list_item.html index 688c4c6..5c4b763 100644 --- a/templates/player_list_item.html +++ b/templates/player_list_item.html @@ -1,21 +1,25 @@ -
-
- +{{ range . }} +
+
+ +
+ {{ .PlayerName }} +
+ {{ .Score }} +
+ + {{ if not (eq .UserRole "READER") }} + + + {{ end }}
- %s -
- %s -
- - - -
\ No newline at end of file +{{ end }} \ No newline at end of file diff --git a/utils/globals.go b/utils/globals.go index 0255223..9c5ce1f 100644 --- a/utils/globals.go +++ b/utils/globals.go @@ -2,6 +2,7 @@ package utils import ( "InfantrySkillCalculator/models" + "html/template" "io" "time" ) @@ -9,3 +10,8 @@ import ( var GinWriter io.Writer = nil var GameMetrics models.GameMetrics var PlayerCacheLifetime = 24 * time.Hour + +var MainPageTemplates *template.Template +var LoginPageTemplates *template.Template +var RegisterPageTemplates *template.Template +var PlayerItemTemplate *template.Template