diff --git a/.idea/InfantrySkillCalculator.iml b/.idea/InfantrySkillCalculator.iml index 5c5755c..ef9a3ae 100644 --- a/.idea/InfantrySkillCalculator.iml +++ b/.idea/InfantrySkillCalculator.iml @@ -7,5 +7,7 @@ + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 21f223c..4dc3c2e 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,12 +4,13 @@ diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml index 4c9360b..b5285cc 100644 --- a/.idea/jsLibraryMappings.xml +++ b/.idea/jsLibraryMappings.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/auth.go b/auth.go index d4d6dbf..8556d60 100644 --- a/auth.go +++ b/auth.go @@ -50,37 +50,56 @@ func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) if auth, ok := session.Values["authenticated"].(bool); !ok || !auth || !controllers.IsUserEnabled(session.Values["username"].(string)) { - session.Options.MaxAge = -1 - err := session.Save(c.Request, c.Writer) - if err != nil { - log.Fatal(err) - } - c.Redirect(http.StatusFound, "/login") - c.Abort() + redirectToLogin(c) return } c.Next() } } +func AdminAuthRequired() gin.HandlerFunc { + return func(c *gin.Context) { + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) + if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { + redirectToLogin(c) + return + } + + username, ok := session.Values["username"].(string) + if !ok || !controllers.IsUserEnabled(username) || !controllers.IsUserAdmin(username) { + redirectToLogin(c) + return + } + + c.Next() + } +} + +func isUserAdmin(c *gin.Context) bool { + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) + username, ok := session.Values["username"].(string) + if !ok { + return false + } + return controllers.IsUserAdmin(username) +} + +func redirectToLogin(c *gin.Context) { + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) + session.Options.MaxAge = -1 + err := session.Save(c.Request, c.Writer) + if err != nil { + log.Fatal(err) + } + c.Redirect(http.StatusFound, "/login") + c.Abort() +} + func isValidCode(code string) bool { var activationCode models.ActivationCode if err := models.DB.Where("code = ?", code).First(&activationCode).Error; err != nil { return false } - if activationCode.Code == code && !activationCode.Used { - models.DB.Model(&activationCode).Updates(map[string]interface{}{ - "Code": code, - "Used": true, - }) - - newCode := utils.GenerateActivationCode() - newCodeObj := models.ActivationCode{Code: newCode, Used: false} - models.DB.Create(&newCodeObj) - - return true - } - - return false + return activationCode.Code == code && activationCode.UsedForUsername == "" } diff --git a/controllers/cache_controller.go b/controllers/cache_controller.go index 7953365..2ac4e63 100644 --- a/controllers/cache_controller.go +++ b/controllers/cache_controller.go @@ -5,6 +5,7 @@ import ( "InfantrySkillCalculator/utils" "github.com/gin-gonic/gin" "gorm.io/gorm" + "gorm.io/gorm/clause" "net/http" "time" ) @@ -49,8 +50,6 @@ func AddCache(c *gin.Context) { cache := models.PlayerCache{CacheDate: input.CacheDate, PlayerID: input.PlayerID, Score: input.Score, Game: input.Game} - //CreateOrUpdateCache(cache) - c.JSON(http.StatusOK, cache) } @@ -68,15 +67,11 @@ func UpdateCacheByPlayerID(c *gin.Context) { return } - //log.Println("Updating cache for player #" + utils.UintToString(cache.PlayerID)) - models.DB.Model(&cache).Updates(map[string]interface{}{ "CacheDate": input.CacheDate, "Score": input.Score, }) - //UpdateCacheTimestamp() - c.JSON(http.StatusOK, cache) } @@ -90,11 +85,21 @@ func DeleteCacheByPlayerID(c *gin.Context) { models.DB.Delete(&cache) - //UpdateCacheTimestamp() - c.JSON(http.StatusOK, true) +} - //log.Println("Deleted cache for player #" + utils.UintToString(cache.PlayerID)) +// DeleteAllCaches DELETE /cache +func DeleteAllCaches(c *gin.Context) { + var caches []models.PlayerCache + if err := models.DB. + Session(&gorm.Session{AllowGlobalUpdate: true}). + Clauses(clause.Returning{}). + Delete(&caches).Error; err != nil { + c.String(http.StatusBadRequest, "Purge failed! Error: "+err.Error()) + return + } + + c.String(http.StatusOK, "Purged "+utils.UintToString(uint(len(caches)))+" caches!") } func FindCacheGin(out interface{}, c *gin.Context) *gorm.DB { diff --git a/controllers/code_controller.go b/controllers/code_controller.go new file mode 100644 index 0000000..f36eb31 --- /dev/null +++ b/controllers/code_controller.go @@ -0,0 +1,37 @@ +package controllers + +import ( + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" + "github.com/gin-gonic/gin" + "net/http" +) + +// CreateCode POST /code +func CreateCode(c *gin.Context) { + userRole, ok := c.GetPostForm("user_role") + if !ok { + c.String(http.StatusBadRequest, "Missing user role") + return + } + var role models.Role + switch userRole { + case "ADMIN": + role = models.AdminRole + case "AUTHOR": + role = models.AuthorRole + case "READER": + role = models.ReaderRole + default: + c.String(http.StatusInternalServerError, "Invalid user role: "+userRole) + } + + newCode := utils.GenerateActivationCode() + newCodeObj := models.ActivationCode{Code: newCode, UserRole: role} + if err := models.DB.Create(&newCodeObj).Error; err != nil { + c.String(http.StatusInternalServerError, "Failed to create new code: "+err.Error()) + return + } + + c.String(http.StatusOK, "Activation code for role '"+string(role)+"': "+newCode) +} diff --git a/controllers/user_controller.go b/controllers/user_controller.go index c85ad60..657dd56 100644 --- a/controllers/user_controller.go +++ b/controllers/user_controller.go @@ -7,17 +7,39 @@ import ( func CreateUser(username string, hashedPassword string, enabled bool, usedCode string) { user := models.User{Username: username, Password: hashedPassword, Enabled: enabled} - models.DB.Create(&user) - - err := models.DB.Model(&models.ActivationCode{}). - Where("code = ?", usedCode). - Update("Used", true).Error + err := models.DB.Create(&user).Error if err != nil { - log.Fatal(err) + log.Fatalf("Error while creating user: %v", err) + } + + var code models.ActivationCode + err = models.DB. + Model(&models.ActivationCode{}). + Where("code = ?", usedCode). + First(&code).Error + if err != nil { + log.Fatalf("Error while getting activation code: %v", err) + } + + code.UsedForUsername = username + err = models.DB.Save(&code).Error + if err != nil { + log.Fatalf("Error while updating activation code: %v", err) + } + + user.UserRole = code.UserRole + err = models.DB.Save(&user).Error + if err != nil { + log.Fatalf("Error while updating user role: %v", err) } var bf2042 models.Game - models.DB.Where("tag = ?", "BF2042").First(&bf2042) + err = models.DB. + Where("tag = ?", "BF2042"). + First(&bf2042).Error + if err != nil { + log.Fatalf("Error while getting game: %v", err) + } userSettings := models.UserSettings{ Username: username, ActiveGameID: bf2042.ID, @@ -33,3 +55,13 @@ func IsUserEnabled(username string) bool { models.DB.Where("username = ?", username).First(&user) return user.Enabled } + +func IsUserAdmin(username string) bool { + var user models.User + err := models.DB.Where("username = ?", username).First(&user).Error + if err != nil { + log.Fatal(err) + return false + } + return user.UserRole == models.AdminRole +} diff --git a/main.go b/main.go index 63e030d..ddf4dc0 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,8 @@ func main() { router.LoadHTMLGlob("templates/**/*") protected := router.Group("/") protected.Use(AuthRequired()) + admin := router.Group("/admin") + admin.Use(AdminAuthRequired()) models.ConnectDatabase() @@ -35,6 +37,10 @@ func main() { gin.LoggerWithWriter(utils.GinWriter), gin.Recovery(), ) + admin.Use( + gin.LoggerWithWriter(utils.GinWriter), + gin.Recovery(), + ) router.Static("/static", "./static") @@ -67,6 +73,9 @@ func main() { protected.GET("/settings", controllers.GetSettings) protected.PATCH("/settings", controllers.UpdateSettings) + admin.GET("/clear_cache", controllers.DeleteAllCaches) + admin.POST("/create_code", controllers.CreateCode) + log.Println("Running on 8000...") log.Fatal(router.Run(":8000")) } diff --git a/models/code.go b/models/code.go index 544633b..8be3b90 100644 --- a/models/code.go +++ b/models/code.go @@ -1,6 +1,8 @@ package models type ActivationCode struct { - Code string - Used bool + Code string `gorm:"primary_key"` + UserRole Role + UsedForUsername string + UsedFor User `gorm:"foreignKey:UsedForUsername"` } diff --git a/models/setup.go b/models/setup.go index 82a9b6f..1ee3e91 100644 --- a/models/setup.go +++ b/models/setup.go @@ -44,8 +44,8 @@ func ConnectDatabase() { var code ActivationCode if err := database.First(&code).Error; err != nil { firstCode := utils.GenerateActivationCode() - database.Create(&ActivationCode{Code: firstCode, Used: false}) - log.Println("Created first activation code: " + firstCode) + database.Create(&ActivationCode{Code: firstCode, UserRole: AdminRole}) + log.Println("Created first activation code with ADMIN role:\n" + firstCode) } } err = database.AutoMigrate(&Game{}) diff --git a/models/user.go b/models/user.go index e5a8791..97885c8 100644 --- a/models/user.go +++ b/models/user.go @@ -4,4 +4,13 @@ type User struct { Username string `json:"username" gorm:"primary_key"` Password string `json:"password"` Enabled bool `json:"enabled" default:"1"` + UserRole Role `json:"user_role" default:"READER"` } + +type Role string + +const ( + AdminRole Role = "ADMIN" + AuthorRole Role = "AUTHOR" + ReaderRole Role = "READER" +) diff --git a/pages.go b/pages.go index 8af5483..161bd55 100644 --- a/pages.go +++ b/pages.go @@ -2,7 +2,6 @@ package main import ( "InfantrySkillCalculator/controllers" - "InfantrySkillCalculator/models" "InfantrySkillCalculator/utils" "github.com/gin-gonic/gin" "html/template" @@ -32,11 +31,8 @@ func mainPage(c *gin.Context) { log.Fatal(err) } - var clans []models.Clan - models.DB.Find(&clans) - data := map[string]interface{}{ - "clans": clans, + "isAdmin": isUserAdmin(c), } err = tmpl.Execute(c.Writer, data) diff --git a/static/index.js b/static/index.js index 1d4041b..d51792e 100644 --- a/static/index.js +++ b/static/index.js @@ -144,3 +144,39 @@ function deselectAllPlayers(playerListId) { checkCounter.innerText = 0; } + +function confirmAndTrigger(btn) { + Swal.fire({ + title: btn.innerText, + text: 'Do you want to continue?', + confirmButtonText: 'Yes', + confirmButtonColor: '#dd6b55', + denyButtonColor: '#3085d6', + icon: 'warning', + showDenyButton: true + }).then((result) => { + if (result.isConfirmed) { + htmx.trigger(btn, 'confirmed'); + } + }); +} + +function createCodeDialog(btn) { + Swal.fire({ + title: btn.innerText, + input: 'select', + inputOptions: { + 'READER': 'Reader', + 'AUTHOR': 'Author', + 'ADMIN': 'Admin' + }, + inputPlaceholder: 'Select a role', + confirmButtonText: 'Generate', + showCancelButton: true + }).then((result) => { + if (result.isConfirmed) { + btn.setAttribute('hx-vals', '{"user_role": "' + result.value + '"}'); + htmx.trigger(btn, 'confirmed'); + } + }); +} diff --git a/templates/components/bottom_controls.html b/templates/components/bottom_controls.html index e730823..f5d7df3 100644 --- a/templates/components/bottom_controls.html +++ b/templates/components/bottom_controls.html @@ -2,13 +2,77 @@
- - - - +
+ + {{ end }} \ No newline at end of file diff --git a/templates/components/header.html b/templates/components/header.html index 2eeff05..8b4ce18 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -9,6 +9,8 @@ + + {{ end }} \ No newline at end of file