From e5d13f2270832b06de9965918777f2a095a317d4 Mon Sep 17 00:00:00 2001
From: MaxJa4 <74194322+MaxJa4@users.noreply.github.com>
Date: Sat, 20 Jan 2024 16:47:22 +0100
Subject: [PATCH] Add admin-route & dropdown-menu. Add clear-cache &
create-code. Adjustments for activation-code and user models. Add sweet-alert
for admin-tools.
---
.idea/InfantrySkillCalculator.iml | 2 +
.idea/inspectionProfiles/Project_Default.xml | 3 +-
.idea/jsLibraryMappings.xml | 2 +-
auth.go | 61 ++++++++-----
controllers/cache_controller.go | 23 +++--
controllers/code_controller.go | 37 ++++++++
controllers/user_controller.go | 46 ++++++++--
main.go | 9 ++
models/code.go | 6 +-
models/setup.go | 4 +-
models/user.go | 9 ++
pages.go | 6 +-
static/index.js | 36 ++++++++
templates/components/bottom_controls.html | 90 ++++++++++++++++++--
templates/components/header.html | 2 +
15 files changed, 281 insertions(+), 55 deletions(-)
create mode 100644 controllers/code_controller.go
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 @@
-
-
-
-
-
- Einstellungen
-
+
@@ -24,4 +88,16 @@
+
+
{{ 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