diff --git a/.idea/InfrantrySkillCalculator.iml b/.idea/InfantrySkillCalculator.iml similarity index 100% rename from .idea/InfrantrySkillCalculator.iml rename to .idea/InfantrySkillCalculator.iml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index 0b86ae1..21f223c 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -4,11 +4,12 @@ diff --git a/.idea/modules.xml b/.idea/modules.xml index c97311f..174c132 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/auth.go b/auth.go index f47c93a..d4d6dbf 100644 --- a/auth.go +++ b/auth.go @@ -1,8 +1,9 @@ package main import ( - "InfrantrySkillCalculator/models" - "InfrantrySkillCalculator/utils" + "InfantrySkillCalculator/controllers" + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" "errors" "fmt" "github.com/gin-gonic/gin" @@ -34,14 +35,7 @@ func getUserPassword(username string) (string, error) { return "", err } - var hashedPW string - hashedPW, err := hashPassword(user.Password) - if err != nil { - log.Fatal(err) - return "", err - } - - return hashedPW, nil + return user.Password, nil } func hashPassword(password string) (string, error) { @@ -54,8 +48,13 @@ func hashPassword(password string) (string, error) { func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { - session, _ := store.Get(c.Request, LoginSessionName) - if auth, ok := session.Values["authenticated"].(bool); !ok || !auth { + 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() return diff --git a/controllers/cache_controller.go b/controllers/cache_controller.go index 0479923..7953365 100644 --- a/controllers/cache_controller.go +++ b/controllers/cache_controller.go @@ -1,8 +1,8 @@ package controllers import ( - "InfrantrySkillCalculator/models" - "InfrantrySkillCalculator/utils" + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" "github.com/gin-gonic/gin" "gorm.io/gorm" "net/http" diff --git a/controllers/clan_controller.go b/controllers/clan_controller.go index f99bcae..f87c613 100644 --- a/controllers/clan_controller.go +++ b/controllers/clan_controller.go @@ -1,14 +1,13 @@ package controllers import ( - "InfrantrySkillCalculator/models" - "InfrantrySkillCalculator/utils" + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" "fmt" "github.com/gin-gonic/gin" "gorm.io/gorm" "log" "net/http" - "strconv" ) type AddClanInput struct { @@ -66,8 +65,6 @@ func AddClan(c *gin.Context) { clan = models.Clan{Name: input.Name, Tag: input.Tag, KeepUpdated: input.KeepUpdated} models.DB.Create(&clan) - //UpdateClanTimestamp() - c.JSON(http.StatusOK, clan) _, err := fmt.Fprintf(utils.GinWriter, "Added clan '"+clan.Name+"' with tag '"+clan.Tag+"'\n") @@ -90,45 +87,30 @@ func GetClanByID(c *gin.Context) { // UpdateClanByID PATCH /clan/:id func UpdateClanByID(c *gin.Context) { - var clan models.Clan - if err := FindClanByID(&clan, c).Error; err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"}) - return - } - var input UpdateClanInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - msg := "Updating clan '" + clan.Name + "'#" + strconv.FormatUint(uint64(clan.ID), 10) - if clan.Name != input.Name { - msg += " (new: '" + input.Name + "')" - } - msg += " with tag '" + clan.Tag + "'" - if clan.Tag != input.Tag { - msg += " (new: '" + input.Tag + "')" - } - msg += " with updating '" + strconv.FormatBool(clan.KeepUpdated) + "'" - if clan.KeepUpdated != input.KeepUpdated { - msg += " (new: '" + strconv.FormatBool(input.KeepUpdated) + "')" + res := models.DB.Model(&models.Clan{}). + Where("id = ?", c.Param("id")). + Updates(map[string]interface{}{ + "Name": input.Name, + "Tag": input.Tag, + "KeepUpdated": input.KeepUpdated, + }) + if res.Error != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": res.Error.Error()}) + return } - _, err := fmt.Fprintf(utils.GinWriter, msg+"\n") + c.JSON(http.StatusOK, nil) + + _, err := fmt.Fprintf(utils.GinWriter, "Updated clan '"+input.Name+"' with tag '"+input.Tag+"'\n") if err != nil { log.Fatal(err) } - - models.DB.Model(&clan).Updates(map[string]interface{}{ - "Name": input.Name, - "Tag": input.Tag, - "KeepUpdated": input.KeepUpdated, - }) - - //UpdateClanTimestamp() - - c.JSON(http.StatusOK, clan) } // DeleteClanByID DELETE /clan/:id @@ -141,8 +123,6 @@ func DeleteClanByID(c *gin.Context) { models.DB.Delete(&clan) - //UpdateClanTimestamp() - c.JSON(http.StatusOK, true) _, err := fmt.Fprintf(utils.GinWriter, "Deleted clan '"+clan.Name+"' with tag '"+clan.Tag+"'\n") diff --git a/controllers/game_controller.go b/controllers/game_controller.go index 58733ca..12de964 100644 --- a/controllers/game_controller.go +++ b/controllers/game_controller.go @@ -1,7 +1,8 @@ package controllers import ( - "InfrantrySkillCalculator/models" + "InfantrySkillCalculator/models" + "fmt" "github.com/gin-gonic/gin" "gorm.io/gorm" "net/http" @@ -25,6 +26,21 @@ func GetGames(c *gin.Context) { c.JSON(http.StatusOK, games) } +// GetGamesHTML GET /game_html +func GetGamesHTML(c *gin.Context) { + var games []models.Game + models.DB.Find(&games) + + var htmlOptions string + htmlOptions = `` + for _, game := range games { + htmlOptions += fmt.Sprintf(``, game.ID, game.Name) + } + + c.Header("Content-Type", "text/html") + c.String(http.StatusOK, htmlOptions) +} + // GetGameByID GET /game/:id func GetGameByID(c *gin.Context) { var game models.Game diff --git a/controllers/player_controller.go b/controllers/player_controller.go index 74d2ed4..7c8d607 100644 --- a/controllers/player_controller.go +++ b/controllers/player_controller.go @@ -1,15 +1,14 @@ package controllers import ( - "InfrantrySkillCalculator/models" - "InfrantrySkillCalculator/utils" + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" "fmt" "github.com/gin-gonic/gin" "gorm.io/gorm" "log" "net/http" "os" - "strconv" ) type AddPlayerInput struct { @@ -70,8 +69,6 @@ func AddPlayer(c *gin.Context) { player = models.Player{Name: input.Name, ClanID: input.ClanID} models.DB.Create(&player) - //UpdatePlayerTimestamp() - c.JSON(http.StatusOK, player) _, err := fmt.Fprintf(utils.GinWriter, "Added player '"+player.Name+"'\n") @@ -105,46 +102,29 @@ func GetPlayerIDByName(c *gin.Context) { // UpdatePlayerByID PATCH /player/:id func UpdatePlayerByID(c *gin.Context) { - player := FindPlayerByID(utils.StringToUint(c.Param("id"))) - if player == nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"}) - return - } - var input UpdatePlayerInput if err := c.ShouldBindJSON(&input); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if player.Name != input.Name { - if err := FindPlayerByName(&player, c).Error; err == nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Player with this name already exists!"}) - return - } + res := models.DB.Model(&models.Player{}). + Where("id = ?", utils.StringToUint(c.Param("id"))). + Updates(map[string]interface{}{ + "Name": input.Name, + "ClanID": input.ClanID, + }) + if res.Error != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": res.Error.Error()}) + return } - msg := "Updating player '" + player.Name + "' #" + strconv.FormatUint(uint64(player.ID), 10) - if player.Name != input.Name { - msg += " (new: '" + input.Name + "')" - } - msg += " with clan #" + utils.UintToString(player.ClanID) - if player.ClanID != input.ClanID { - msg += " (new: #" + utils.UintToString(input.ClanID) + ")" - } - _, err := fmt.Fprintf(utils.GinWriter, msg+"\n") + c.JSON(http.StatusOK, nil) + + _, err := fmt.Fprintf(utils.GinWriter, "Updated player '"+input.Name+"'\n") if err != nil { log.Fatal(err) } - - models.DB.Model(&player).Updates(map[string]interface{}{ - "Name": input.Name, - "ClanID": input.ClanID, - }) - - //UpdatePlayerTimestamp() - - c.JSON(http.StatusOK, player) } // DeletePlayerByID DELETE /player/:id @@ -157,8 +137,6 @@ func DeletePlayerByID(c *gin.Context) { models.DB.Delete(&player) - //UpdatePlayerTimestamp() - c.JSON(http.StatusOK, true) _, err := fmt.Fprintf(utils.GinWriter, "Deleted player '"+player.Name+"'\n") diff --git a/controllers/user_controller.go b/controllers/user_controller.go new file mode 100644 index 0000000..c85ad60 --- /dev/null +++ b/controllers/user_controller.go @@ -0,0 +1,35 @@ +package controllers + +import ( + "InfantrySkillCalculator/models" + "log" +) + +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 + if err != nil { + log.Fatal(err) + } + + var bf2042 models.Game + models.DB.Where("tag = ?", "BF2042").First(&bf2042) + userSettings := models.UserSettings{ + Username: username, + ActiveGameID: bf2042.ID, + SquadColors: true, + CalcMedian: false, + UseCache: true, + } + models.DB.Create(&userSettings) +} + +func IsUserEnabled(username string) bool { + var user models.User + models.DB.Where("username = ?", username).First(&user) + return user.Enabled +} diff --git a/controllers/user_settings_controller.go b/controllers/user_settings_controller.go new file mode 100644 index 0000000..f9e7283 --- /dev/null +++ b/controllers/user_settings_controller.go @@ -0,0 +1,62 @@ +package controllers + +import ( + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" + "github.com/gin-gonic/gin" + "net/http" +) + +type UpdateSettingsInput struct { + ActiveGameID uint `json:"active_game_id"` + SquadColors bool `json:"squad_colors"` + CalcMedian bool `json:"calc_median"` + UseCache bool `json:"use_cache"` +} + +// GetSettings GET /settings +func GetSettings(c *gin.Context) { + var settings models.UserSettings + + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) + var username string + username, _ = session.Values["username"].(string) + + if err := models.DB.Where("username = ?", username).First(&settings).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "No settings available!"}) + return + } + + sanitizedSettings := map[string]interface{}{ + "active_game_id": settings.ActiveGameID, + "squad_colors": settings.SquadColors, + "calc_median": settings.CalcMedian, + "use_cache": settings.UseCache, + } + + c.JSON(http.StatusOK, sanitizedSettings) +} + +// UpdateSettings PATCH /settings +func UpdateSettings(c *gin.Context) { + var settings models.UserSettings + + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) + var username string + username, _ = session.Values["username"].(string) + + if err := models.DB.Where("username = ?", username).First(&settings).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "No settings available!"}) + return + } + + var input UpdateSettingsInput + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + models.DB.Model(&settings).Updates(input) + + c.JSON(http.StatusOK, nil) +} diff --git a/go.mod b/go.mod index 818314e..511a4ca 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module InfrantrySkillCalculator +module InfantrySkillCalculator go 1.21 diff --git a/main.go b/main.go index c937752..63e030d 100644 --- a/main.go +++ b/main.go @@ -1,21 +1,16 @@ package main import ( - "InfrantrySkillCalculator/controllers" - "InfrantrySkillCalculator/models" - "InfrantrySkillCalculator/utils" + "InfantrySkillCalculator/controllers" + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" "github.com/gin-gonic/gin" - "github.com/gorilla/sessions" _ "github.com/gorilla/sessions" "io" "log" "os" ) -var store = sessions.NewCookieStore([]byte("f0q0qew0!)§(ds9713lsda231")) - -const LoginSessionName = "session" - func main() { //gin.SetMode(gin.ReleaseMode) // uncomment upon release @@ -33,7 +28,11 @@ func main() { f, _ := os.OpenFile("isc_rest.log", os.O_RDWR|os.O_APPEND|os.O_CREATE, 0660) utils.GinWriter = io.MultiWriter(f, os.Stdout) router.Use( - gin.LoggerWithWriter(utils.GinWriter, "/update"), + gin.LoggerWithWriter(utils.GinWriter, "/static"), + gin.Recovery(), + ) + protected.Use( + gin.LoggerWithWriter(utils.GinWriter), gin.Recovery(), ) @@ -62,6 +61,12 @@ func main() { protected.PATCH("/player/:id", controllers.UpdatePlayerByID) protected.DELETE("/player/:id", controllers.DeletePlayerByID) + protected.GET("/game", controllers.GetGames) + protected.GET("/game_html", controllers.GetGamesHTML) + + protected.GET("/settings", controllers.GetSettings) + protected.PATCH("/settings", controllers.UpdateSettings) + log.Println("Running on 8000...") log.Fatal(router.Run(":8000")) } diff --git a/models/game.go b/models/game.go index 2067d63..5491d35 100644 --- a/models/game.go +++ b/models/game.go @@ -4,12 +4,11 @@ type Game struct { ID uint `json:"id" gorm:"primary_key"` Name string `json:"name" binding:"required"` Tag string `json:"tag" binding:"required"` - Settings []GameSetting `json:"settings" gorm:"foreignKey:GameID"` + Settings []GameSetting `json:"settings" gorm:"foreignKey:ID"` } type GameSetting struct { ID uint `json:"id" gorm:"primary_key"` - GameID uint `json:"game_id"` Name string `json:"name" binding:"required"` WeaponCategory string `json:"weapon_category"` Value float64 `json:"value" binding:"required"` diff --git a/models/setup.go b/models/setup.go index e8e7846..82a9b6f 100644 --- a/models/setup.go +++ b/models/setup.go @@ -1,7 +1,7 @@ package models import ( - "InfrantrySkillCalculator/utils" + "InfantrySkillCalculator/utils" "gorm.io/driver/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" @@ -51,11 +51,23 @@ func ConnectDatabase() { err = database.AutoMigrate(&Game{}) if err != nil { log.Fatal(err) + } else { + var game Game + if err := database.First(&game).Error; err != nil { + database.Create(&Game{Name: "Battlefield V", Tag: "BFV"}) + database.Create(&Game{Name: "Battlefield 2042", Tag: "BF2042"}) + log.Println("Created first games") + } } + err = database.AutoMigrate(&MetricSetting{}) if err != nil { log.Fatal(err) } + err = database.AutoMigrate(&UserSettings{}) + if err != nil { + log.Fatal(err) + } DB = database } diff --git a/models/user_settings.go b/models/user_settings.go new file mode 100644 index 0000000..291f3af --- /dev/null +++ b/models/user_settings.go @@ -0,0 +1,12 @@ +package models + +type UserSettings struct { + ID uint `json:"id" gorm:"primary_key"` + Username string `json:"username"` + User User `json:"user" gorm:"foreignKey:Username;references:Username"` + ActiveGameID uint `json:"active_game_id"` + ActiveGame Game `json:"active_game" gorm:"foreignKey:ID;references:ActiveGameID"` + SquadColors bool `json:"squad_colors" default:"1"` + CalcMedian bool `json:"calc_median" default:"0"` + UseCache bool `json:"use_cache" default:"1"` +} diff --git a/pages.go b/pages.go index 1847e83..8af5483 100644 --- a/pages.go +++ b/pages.go @@ -1,7 +1,9 @@ package main import ( - "InfrantrySkillCalculator/models" + "InfantrySkillCalculator/controllers" + "InfantrySkillCalculator/models" + "InfantrySkillCalculator/utils" "github.com/gin-gonic/gin" "html/template" "log" @@ -44,7 +46,7 @@ func mainPage(c *gin.Context) { } func loginPage(c *gin.Context) { - session, _ := store.Get(c.Request, LoginSessionName) + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) if auth, ok := session.Values["authenticated"].(bool); ok && auth { c.Redirect(http.StatusFound, "/") @@ -71,7 +73,7 @@ func loginPost(c *gin.Context) { return } - session, _ := store.Get(c.Request, LoginSessionName) + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) session.Values["authenticated"] = true session.Values["username"] = username err := session.Save(c.Request, c.Writer) @@ -85,7 +87,7 @@ func loginPost(c *gin.Context) { } func logout(c *gin.Context) { - session, _ := store.Get(c.Request, LoginSessionName) + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) session.Values["authenticated"] = false err := session.Save(c.Request, c.Writer) if err != nil { @@ -97,7 +99,7 @@ func logout(c *gin.Context) { } func registerPage(c *gin.Context) { - session, _ := store.Get(c.Request, LoginSessionName) + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) if auth, ok := session.Values["authenticated"].(bool); ok && auth { c.Redirect(http.StatusFound, "/") @@ -125,13 +127,18 @@ func registerPost(c *gin.Context) { return } - user := models.User{Username: username, Password: password, Enabled: true} - models.DB.Create(&user) + hashedPassword, err := hashPassword(password) + if err != nil { + c.HTML(http.StatusOK, "login_error.html", gin.H{"message": "Fehler beim Registrieren!"}) + return + } - session, _ := store.Get(c.Request, LoginSessionName) + controllers.CreateUser(username, hashedPassword, true, code) + + session, _ := utils.Store.Get(c.Request, utils.LoginSessionName) session.Values["authenticated"] = true session.Values["username"] = username - err := session.Save(c.Request, c.Writer) + err = session.Save(c.Request, c.Writer) if err != nil { c.JSON(http.StatusInternalServerError, nil) return diff --git a/templates/modals/add_player.html b/templates/modals/add_player.html index a20e9ce..d7dc73d 100644 --- a/templates/modals/add_player.html +++ b/templates/modals/add_player.html @@ -37,7 +37,7 @@ const submitButton = addPlayerModal.querySelector('button[name="submit"]'); const playerName = addPlayerModal.querySelector('#playerName'); const clanName = addPlayerModal.querySelector('#playerClanName'); - const errorDiv = editPlayerModal.querySelector('.error-message'); + const errorDiv = addPlayerModal.querySelector('.error-message'); const homeClanListIndex = document.getElementById('home-clan').selectedIndex; const oppClanListIndex = document.getElementById('opponent-clan').selectedIndex; @@ -67,6 +67,12 @@ "Content-type": "application/json; charset=UTF-8" } }) + .then(response => { + if (!response.ok) { + throw new Error('Hinzufügen fehlgeschlagen!\nSpielername existiert möglichweise bereits.'); + } + return response.text(); + }) .then(() => { const sameClan = homeClanListIndex === oppClanListIndex; if (playerList.id === 'home-player-list' || sameClan) diff --git a/templates/modals/edit_player.html b/templates/modals/edit_player.html index 3cc5509..42a65b1 100644 --- a/templates/modals/edit_player.html +++ b/templates/modals/edit_player.html @@ -64,6 +64,12 @@ "Content-type": "application/json; charset=UTF-8" } }) + .then(response => { + if (!response.ok) { + throw new Error('Änderung fehlgeschlagen!\nSpielername existiert möglichweise bereits.'); + } + return response.text(); + }) .then(() => { const sameClan = homeClanListIndex === oppClanListIndex; if (playerList.id === 'home-player-list' || sameClan) diff --git a/templates/modals/settings.html b/templates/modals/settings.html index a9ebdb7..534a451 100644 --- a/templates/modals/settings.html +++ b/templates/modals/settings.html @@ -13,7 +13,7 @@
-
@@ -41,7 +41,79 @@ {{ end }} \ No newline at end of file diff --git a/utils/session.go b/utils/session.go new file mode 100644 index 0000000..e71c00a --- /dev/null +++ b/utils/session.go @@ -0,0 +1,7 @@ +package utils + +import "github.com/gorilla/sessions" + +var Store = sessions.NewCookieStore([]byte("f0q0qew0!)§(ds9713lsda231")) + +const LoginSessionName = "session"