From 16d782fbe85f76bf361675840b0b599d32f8c67a Mon Sep 17 00:00:00 2001
From: MaxJa4 <74194322+MaxJa4@users.noreply.github.com>
Date: Sun, 21 Jan 2024 00:49:20 +0100
Subject: [PATCH] Bugfixes. Optimizations/refactor. Add redis for player-cache.
Add docker files. Replace sqlite dep. Single-Calc for existing players.
Game-Metrics in JSON.
---
.dockerignore | 6 +
.gitignore | 1 +
.idea/inspectionProfiles/Go_Error.xml | 427 ++++++++++++++++++++++
.idea/runConfigurations/Redis.xml | 9 +
Dockerfile | 24 ++
auth.go | 27 +-
config/metrics.json | 88 +++++
controllers/cache_controller.go | 136 ++++---
controllers/metrics_controller.go | 32 ++
controllers/player_controller.go | 21 +-
controllers/user_settings_controller.go | 43 ++-
docker-compose.yml | 18 +
go.mod | 21 +-
go.sum | 28 +-
go.work | 7 +
go.work.sum | 31 ++
internal/cache/cache.go | 55 +++
internal/cache/go.mod | 3 +
internal/session/go.mod | 3 +
internal/session/session.go | 43 +++
main.go | 15 +-
models/cache.go | 11 -
models/metric_setting.go | 9 -
models/metrics_json.go | 18 +
models/setup.go | 30 +-
models/tracker_json.go | 16 +
pages.go | 27 +-
static/index.css | 3 +-
static/index.js | 12 +-
templates/components/bottom_controls.html | 4 +-
templates/modals/add_clan.html | 4 +-
templates/modals/add_player.html | 12 +-
templates/modals/delete_clan.html | 1 +
templates/modals/delete_player.html | 6 +-
templates/modals/edit_clan.html | 4 +-
templates/modals/edit_player.html | 8 +-
templates/modals/settings.html | 4 +-
templates/player_list_item.html | 8 +-
utils/globals.go | 6 +-
utils/score.go | 129 +++++++
utils/session.go | 7 -
41 files changed, 1154 insertions(+), 203 deletions(-)
create mode 100644 .dockerignore
create mode 100644 .idea/inspectionProfiles/Go_Error.xml
create mode 100644 Dockerfile
create mode 100644 config/metrics.json
create mode 100644 controllers/metrics_controller.go
create mode 100644 docker-compose.yml
create mode 100644 go.work
create mode 100644 go.work.sum
create mode 100644 internal/cache/cache.go
create mode 100644 internal/cache/go.mod
create mode 100644 internal/session/go.mod
create mode 100644 internal/session/session.go
delete mode 100644 models/cache.go
delete mode 100644 models/metric_setting.go
create mode 100644 models/metrics_json.go
create mode 100644 models/tracker_json.go
create mode 100644 utils/score.go
delete mode 100644 utils/session.go
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..3592d05
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,6 @@
+.db
+.log
+redis
+Dockerfile
+docker-compose.yml
+.gitignore
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 2713e6a..1f664fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/isc_rest.log
/isc_data.db
+redis
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Go_Error.xml b/.idea/inspectionProfiles/Go_Error.xml
new file mode 100644
index 0000000..7ebb3e4
--- /dev/null
+++ b/.idea/inspectionProfiles/Go_Error.xml
@@ -0,0 +1,427 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Redis.xml b/.idea/runConfigurations/Redis.xml
index b91b9ae..1264935 100644
--- a/.idea/runConfigurations/Redis.xml
+++ b/.idea/runConfigurations/Redis.xml
@@ -3,6 +3,7 @@
+
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..fe96d36
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,24 @@
+FROM golang:1.21-alpine AS builder
+
+WORKDIR /app
+
+COPY go.mod go.sum internal/**/go.mod ./
+
+RUN go mod download
+
+COPY . .
+
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o isc .
+
+FROM alpine:latest
+
+WORKDIR /root/
+
+COPY --from=builder /app/isc .
+COPY --from=builder /app/templates ./templates
+COPY --from=builder /app/static ./static
+COPY --from=builder /app/config ./config
+
+EXPOSE 8000
+
+CMD ["./isc"]
\ No newline at end of file
diff --git a/auth.go b/auth.go
index 8556d60..6a62525 100644
--- a/auth.go
+++ b/auth.go
@@ -3,12 +3,12 @@ package main
import (
"InfantrySkillCalculator/controllers"
"InfantrySkillCalculator/models"
- "InfantrySkillCalculator/utils"
"errors"
"fmt"
"github.com/gin-gonic/gin"
"golang.org/x/crypto/bcrypt"
"gorm.io/gorm"
+ "internal/session"
"log"
"net/http"
)
@@ -48,25 +48,24 @@ func hashPassword(password string) (string, error) {
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)) {
+ auth, okAuth := session.GetAuthenticated(c)
+ username, okUser := session.GetUsername(c)
+
+ if !okAuth || !okUser || !auth || !controllers.IsUserEnabled(username) {
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
- }
+ auth, okAuth := session.GetAuthenticated(c)
+ username, okUser := session.GetUsername(c)
- username, ok := session.Values["username"].(string)
- if !ok || !controllers.IsUserEnabled(username) || !controllers.IsUserAdmin(username) {
+ if !okAuth || !okUser || !auth || !controllers.IsUserEnabled(username) || !controllers.IsUserAdmin(username) {
redirectToLogin(c)
return
}
@@ -76,8 +75,7 @@ func AdminAuthRequired() gin.HandlerFunc {
}
func isUserAdmin(c *gin.Context) bool {
- session, _ := utils.Store.Get(c.Request, utils.LoginSessionName)
- username, ok := session.Values["username"].(string)
+ username, ok := session.GetUsername(c)
if !ok {
return false
}
@@ -85,10 +83,7 @@ func isUserAdmin(c *gin.Context) bool {
}
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 {
+ if err := session.InvalidateSession(c); err != nil {
log.Fatal(err)
}
c.Redirect(http.StatusFound, "/login")
diff --git a/config/metrics.json b/config/metrics.json
new file mode 100644
index 0000000..0dc828c
--- /dev/null
+++ b/config/metrics.json
@@ -0,0 +1,88 @@
+{
+ "GameMetrics": [
+ {
+ "NormalizeFactor": 17.7,
+ "TopWeaponCount": 3,
+ "GameName": "BF5",
+ "WeaponMetrics": [
+ {
+ "WeaponCategory": "Semi-auto rifle",
+ "AccuracyFactor": 1.42,
+ "KpmFactor": 1.19
+ },
+ {
+ "WeaponCategory": "Self-loading rifle",
+ "AccuracyFactor": 2.05,
+ "KpmFactor": 1.19
+ },
+ {
+ "WeaponCategory": "Bolt action rifle",
+ "AccuracyFactor": 2.07,
+ "KpmFactor": 1.0
+ },
+ {
+ "WeaponCategory": "Shotgun",
+ "AccuracyFactor": 5.59,
+ "KpmFactor": 0.934
+ },
+ {
+ "WeaponCategory": "Assault Rifles",
+ "AccuracyFactor": 1.09,
+ "KpmFactor": 1.04
+ },
+ {
+ "WeaponCategory": "Pistol carbine",
+ "AccuracyFactor": 1.13,
+ "KpmFactor": 1.14
+ },
+ {
+ "WeaponCategory": "Bolt action carbine",
+ "AccuracyFactor": 1.74,
+ "KpmFactor": 0.737
+ },
+ {
+ "WeaponCategory": "Smg",
+ "AccuracyFactor": 0.966,
+ "KpmFactor": 1.04
+ },
+ {
+ "WeaponCategory": "LMG",
+ "AccuracyFactor": 0.944,
+ "KpmFactor": 0.919
+ },
+ {
+ "WeaponCategory": "Assault Rifle",
+ "AccuracyFactor": 1.09,
+ "KpmFactor": 1.04
+ },
+ {
+ "WeaponCategory": "Bolt Action",
+ "AccuracyFactor": 2.07,
+ "KpmFactor": 1.0
+ }
+ ]
+ },
+ {
+ "NormalizeFactor": 17.8,
+ "TopWeaponCount": 3,
+ "GameName": "BF2042",
+ "WeaponMetrics": [
+ {
+ "WeaponCategory": "Assault Rifles",
+ "AccuracyFactor": 1.0,
+ "KpmFactor": 1.0
+ },
+ {
+ "WeaponCategory": "LMG",
+ "AccuracyFactor": 0.8,
+ "KpmFactor": 1.238
+ },
+ {
+ "WeaponCategory": "PDW",
+ "AccuracyFactor": 0.92,
+ "KpmFactor": 0.963
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/controllers/cache_controller.go b/controllers/cache_controller.go
index 08da605..b140f60 100644
--- a/controllers/cache_controller.go
+++ b/controllers/cache_controller.go
@@ -3,37 +3,29 @@ package controllers
import (
"InfantrySkillCalculator/models"
"InfantrySkillCalculator/utils"
- "context"
- "errors"
"fmt"
"github.com/gin-gonic/gin"
- "github.com/redis/go-redis/v9"
- "gorm.io/gorm"
- "gorm.io/gorm/clause"
+ "internal/cache"
+ "log"
"net/http"
- "time"
)
type AddCacheInput struct {
- PlayerID uint `json:"player_id" binding:"required"`
- CacheDate time.Time `json:"date" binding:"required"`
- Score float32 `json:"score" gorm:"default:-1.0"`
- Game string `json:"game" binding:"required"`
+ PlayerID uint `json:"player_id" binding:"required"`
+ Score float32 `json:"score" gorm:"default:-1.0"`
+ GameTag string `json:"game_tag" binding:"required"`
}
type UpdateCacheInput struct {
- CacheDate time.Time `json:"date" binding:"required"`
- Score float32 `json:"score" gorm:"default:-1.0"`
+ Score float32 `json:"score" gorm:"default:-1.0"`
}
-var ctx = context.Background()
-
-// GetCacheByPlayerID GET /cache/:player_id?game_id=TAG
+// GetCacheByPlayerID GET /cache/:player_id?game_tag=TAG
func GetCacheByPlayerID(c *gin.Context) {
playerId := utils.StringToUint(c.Param("player_id"))
- gameId := utils.StringToUint(c.Request.URL.Query().Get("game_id"))
+ gameTag := c.Request.URL.Query().Get("game_tag")
- val, err := GetCacheByPlayerIDGorm(playerId, gameId)
+ val, err := cache.GetScore(playerId, gameTag)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
} else {
@@ -41,19 +33,6 @@ func GetCacheByPlayerID(c *gin.Context) {
}
}
-func GetCacheByPlayerIDGorm(playerId uint, gameId uint) (float32, error) {
- key := fmt.Sprintf("player:%d:game:%d", playerId, gameId)
-
- val, err := models.Cache.Get(ctx, key).Result()
- if errors.Is(err, redis.Nil) {
- return -1.0, nil // cache miss
- } else if err != nil {
- return -1.0, err // cache error
- } else {
- return utils.StringToFloat(val), nil // cache hit
- }
-}
-
// AddCache POST /cache
func AddCache(c *gin.Context) {
var input AddCacheInput
@@ -63,69 +42,84 @@ func AddCache(c *gin.Context) {
}
var game models.Game
- if err := FindGameByTag(&game, input.Game).Error; err != nil {
+ if err := FindGameByTag(&game, input.GameTag).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Game not found!"})
return
}
- cache := models.PlayerCache{CacheDate: input.CacheDate, PlayerID: input.PlayerID, Score: input.Score, Game: input.Game}
+ err := cache.SetScore(input.PlayerID, input.GameTag, input.Score)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()})
+ return
+ }
- c.JSON(http.StatusOK, cache)
+ c.JSON(http.StatusOK, nil)
}
// UpdateCacheByPlayerID PATCH /cache/:id?game=TAG
func UpdateCacheByPlayerID(c *gin.Context) {
- var cache models.PlayerCache
- if err := FindCacheGin(&cache, c).Error; err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
- return
+ playerID := utils.StringToUint(c.Param("id"))
+ gameTag := c.Request.URL.Query().Get("game")
+ score := utils.StringToFloat(c.PostForm("score"))
+
+ err := cache.SetScore(playerID, gameTag, score)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()})
+ } else {
+ c.JSON(http.StatusOK, nil)
}
-
- var input UpdateCacheInput
- if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
-
- models.DB.Model(&cache).Updates(map[string]interface{}{
- "CacheDate": input.CacheDate,
- "Score": input.Score,
- })
-
- c.JSON(http.StatusOK, cache)
}
// DeleteCacheByPlayerID DELETE /cache/:id?game=TAG
func DeleteCacheByPlayerID(c *gin.Context) {
- var cache models.PlayerCache
- if err := FindCacheGin(&cache, c).Error; err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
- return
+ playerID := utils.StringToUint(c.Param("id"))
+ gameTag := c.Request.URL.Query().Get("game")
+
+ if err := cache.DeleteScore(playerID, gameTag); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ } else {
+ c.JSON(http.StatusOK, nil)
}
-
- models.DB.Delete(&cache)
-
- c.JSON(http.StatusOK, true)
}
// DeleteAllCaches DELETE /cache
func DeleteAllCaches(c *gin.Context) {
- var caches []models.PlayerCache
+ if err := cache.PurgeCache(); err != nil {
+ c.String(http.StatusBadRequest, err.Error())
+ } else {
+ c.String(http.StatusOK, "Purged all caches!")
+ }
+}
+
+// GetScoreByPlayerID GET /score/:player_id?game_tag=TAG
+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.
- Session(&gorm.Session{AllowGlobalUpdate: true}).
- Clauses(clause.Returning{}).
- Delete(&caches).Error; err != nil {
- c.String(http.StatusBadRequest, "Purge failed! Error: "+err.Error())
+ Model(&models.Player{}).
+ Where("id = ?", playerId).
+ First(&player).Error; err != nil {
+
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Player not found!"})
return
}
- c.String(http.StatusOK, "Purged "+utils.UintToString(uint(len(caches)))+" caches!")
-}
+ score, err := cache.GetScore(player.ID, gameTag)
+ if err != nil || score == -1 {
+ score = utils.CalcPlayerScore(player.Name, gameTag)
+ if score == score && score != -1 { // not NaN
+ if err := cache.SetScore(player.ID, gameTag, score); err != nil {
+ log.Fatal(err)
+ }
+ }
+ }
-func FindCacheGin(out interface{}, c *gin.Context) *gorm.DB {
- return FindCache(out, utils.StringToUint(c.Param("id")), c.Request.URL.Query().Get("game"))
-}
-
-func FindCache(out interface{}, id uint, game string) *gorm.DB {
- return models.DB.Where("player_id = ?", id).Where("game = ?", game).First(out)
+ if score != score || score == -1 { // NaN
+ c.String(http.StatusOK, "")
+ } else if score != -1 {
+ c.String(http.StatusOK, fmt.Sprintf("%.2f", score))
+ } else {
+ c.JSON(http.StatusBadRequest, "Invalid request!")
+ }
}
diff --git a/controllers/metrics_controller.go b/controllers/metrics_controller.go
new file mode 100644
index 0000000..1d9c8e5
--- /dev/null
+++ b/controllers/metrics_controller.go
@@ -0,0 +1,32 @@
+package controllers
+
+import (
+ "InfantrySkillCalculator/models"
+ "InfantrySkillCalculator/utils"
+ "encoding/json"
+ "io"
+ "log"
+ "os"
+)
+
+func LoadMetrics() {
+ f, err := os.Open("./config/metrics.json")
+ if err != nil {
+ log.Fatal("Failed to open metrics.json: ", err)
+ }
+ defer func(f *os.File) {
+ _ = f.Close()
+ }(f)
+
+ data, err := io.ReadAll(f)
+ if err != nil {
+ log.Fatal("Failed to read metrics.json: ", err)
+ }
+
+ var metrics models.GameMetrics
+ if err := json.Unmarshal(data, &metrics); err != nil {
+ log.Fatal("Failed to deserialize metrics.json: ", err)
+ }
+
+ utils.GameMetrics = metrics
+}
diff --git a/controllers/player_controller.go b/controllers/player_controller.go
index 8946346..dc41a0c 100644
--- a/controllers/player_controller.go
+++ b/controllers/player_controller.go
@@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"gorm.io/gorm/clause"
+ "internal/cache"
"log"
"net/http"
"os"
@@ -37,24 +38,36 @@ func GetPlayersByClanHTML(c *gin.Context) {
if err := models.DB.
Where("clan_id = ?", utils.StringToUint(clanId)).
Find(&players).Error; err != nil {
+
+ c.String(http.StatusBadRequest, "")
+ log.Fatal(err)
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, "")
+ log.Fatal(err)
+ return
+ }
+
var htmlOptions string
for _, player := range players {
var score string
- if val, err := GetCacheByPlayerIDGorm(player.ID, 0); err != nil {
- score = "----"
+ if val, err := cache.GetScore(player.ID, game.Tag); err != nil || val == -1.0 {
+ score = ""
} else {
- score = utils.FloatToString(val)
+ score = fmt.Sprintf("%.2f", val)
}
- htmlOptions += fmt.Sprintf(playerItem, player.Name, score, player.ID, player.ID)
+ htmlOptions += fmt.Sprintf(playerItem, player.Name, score, utils.UintToString(player.ID)+"?game_tag="+game.Tag, player.ID, player.ID)
}
c.Header("Content-Type", "text/html")
diff --git a/controllers/user_settings_controller.go b/controllers/user_settings_controller.go
index f9e7283..16ba0a5 100644
--- a/controllers/user_settings_controller.go
+++ b/controllers/user_settings_controller.go
@@ -2,9 +2,10 @@ package controllers
import (
"InfantrySkillCalculator/models"
- "InfantrySkillCalculator/utils"
+ "errors"
"github.com/gin-gonic/gin"
"net/http"
+ "session"
)
type UpdateSettingsInput struct {
@@ -18,9 +19,11 @@ type UpdateSettingsInput struct {
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)
+ username, ok := session.GetUsername(c)
+ if !ok {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Not logged in!"})
+ return
+ }
if err := models.DB.Where("username = ?", username).First(&settings).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No settings available!"})
@@ -37,13 +40,39 @@ func GetSettings(c *gin.Context) {
c.JSON(http.StatusOK, sanitizedSettings)
}
+func GetActiveGame(c *gin.Context) (models.Game, error) {
+ var settings models.UserSettings
+ var game models.Game
+ username, ok := session.GetUsername(c)
+
+ if !ok {
+ err := errors.New("not logged in")
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return models.Game{}, err
+ }
+
+ if err := models.DB.Where("username = ?", username).First(&settings).Error; err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "No settings available!"})
+ return models.Game{}, err
+ }
+
+ if err := models.DB.Where("id = ?", settings.ActiveGameID).First(&game).Error; err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "No active game available!"})
+ return models.Game{}, err
+ }
+
+ return game, nil
+}
+
// 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)
+ username, ok := session.GetUsername(c)
+ if !ok {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Not logged in!"})
+ return
+ }
if err := models.DB.Where("username = ?", username).First(&settings).Error; err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "No settings available!"})
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..6b3e52d
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+version: '3'
+
+services:
+ isc:
+ image: docker.maximilian.link/isc:dev
+ ports:
+ - "127.0.0.1:8000:8000"
+ environment:
+ - GO_ENV=production
+ - REDIS_ADDRESS=redis
+ depends_on:
+ - redis
+ volumes:
+ - ./app:/app
+ redis:
+ image: redis:alpine
+ ports:
+ - "127.0.0.1:6379:6379"
\ No newline at end of file
diff --git a/go.mod b/go.mod
index d6a9aee..1b43980 100644
--- a/go.mod
+++ b/go.mod
@@ -7,21 +7,34 @@ require (
github.com/gorilla/sessions v1.2.2
github.com/redis/go-redis/v9 v9.4.0
golang.org/x/crypto v0.9.0
- gorm.io/driver/sqlite v1.5.4
gorm.io/gorm v1.25.5
)
+require internal/cache v1.0.0
+
+replace internal/cache => ./internal/cache
+
+require (
+ github.com/glebarez/sqlite v1.10.0
+ internal/session v1.0.0
+)
+
+replace internal/session => ./internal/session
+
require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
+ github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
+ github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
@@ -29,10 +42,10 @@ require (
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
- github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
@@ -41,4 +54,8 @@ require (
golang.org/x/text v0.9.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
+ modernc.org/libc v1.22.5 // indirect
+ modernc.org/mathutil v1.5.0 // indirect
+ modernc.org/memory v1.5.0 // indirect
+ modernc.org/sqlite v1.23.1 // indirect
)
diff --git a/go.sum b/go.sum
index 6ef2ffc..7edf668 100644
--- a/go.sum
+++ b/go.sum
@@ -15,12 +15,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
+github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
+github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
+github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -37,6 +43,10 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
+github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
@@ -54,8 +64,6 @@ github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
-github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -67,6 +75,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
+github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -95,8 +106,9 @@ golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
@@ -105,8 +117,14 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
-gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
+modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
+modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
+modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
+modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
+modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/go.work b/go.work
new file mode 100644
index 0000000..0f4511e
--- /dev/null
+++ b/go.work
@@ -0,0 +1,7 @@
+go 1.21
+
+use (
+ .
+ internal/cache
+ internal/session
+)
\ No newline at end of file
diff --git a/go.work.sum b/go.work.sum
new file mode 100644
index 0000000..f6f822d
--- /dev/null
+++ b/go.work.sum
@@ -0,0 +1,31 @@
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
+github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
+github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
+github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
+lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
+modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
+modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
+modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
+modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
+modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
+modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
+modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
+modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
+modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
+modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
+modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY=
+modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
+modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
+modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
+modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
+modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
+rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4=
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
new file mode 100644
index 0000000..6db81bd
--- /dev/null
+++ b/internal/cache/cache.go
@@ -0,0 +1,55 @@
+package cache
+
+import (
+ "InfantrySkillCalculator/models"
+ "InfantrySkillCalculator/utils"
+ "context"
+ "errors"
+ "fmt"
+ "github.com/redis/go-redis/v9"
+)
+
+var ctx = context.Background()
+
+func GetValue(key string) (string, error) {
+ val, err := models.Cache.Get(ctx, key).Result()
+ if err != nil {
+ return "", err // cache miss or error
+ } else {
+ return val, nil // cache hit
+ }
+}
+
+func SetValue(key string, value interface{}) error {
+ return models.Cache.Set(ctx, key, value, utils.PlayerCacheLifetime).Err()
+}
+
+func SetScore(playerId uint, gameTag string, score float32) error {
+ key := GetPlayerCacheKey(playerId, gameTag)
+ return SetValue(key, score)
+}
+
+func GetScore(playerId uint, gameTag string) (float32, error) {
+ key := GetPlayerCacheKey(playerId, gameTag)
+ val, err := GetValue(key)
+ if errors.Is(err, redis.Nil) {
+ return -1.0, nil // cache miss
+ } else if err != nil {
+ return -1.0, err // cache error
+ } else {
+ return utils.StringToFloat(val), nil // cache hit
+ }
+}
+
+func DeleteScore(playerId uint, gameTag string) error {
+ key := GetPlayerCacheKey(playerId, gameTag)
+ return models.Cache.Del(ctx, key).Err()
+}
+
+func PurgeCache() error {
+ return models.Cache.FlushAll(ctx).Err()
+}
+
+func GetPlayerCacheKey(playerId uint, gameTag string) string {
+ return fmt.Sprintf("player:%d:game:%s", playerId, gameTag)
+}
diff --git a/internal/cache/go.mod b/internal/cache/go.mod
new file mode 100644
index 0000000..068cf29
--- /dev/null
+++ b/internal/cache/go.mod
@@ -0,0 +1,3 @@
+module cache
+
+go 1.21
\ No newline at end of file
diff --git a/internal/session/go.mod b/internal/session/go.mod
new file mode 100644
index 0000000..92492da
--- /dev/null
+++ b/internal/session/go.mod
@@ -0,0 +1,3 @@
+module session
+
+go 1.21
\ No newline at end of file
diff --git a/internal/session/session.go b/internal/session/session.go
new file mode 100644
index 0000000..75e8b34
--- /dev/null
+++ b/internal/session/session.go
@@ -0,0 +1,43 @@
+package session
+
+import (
+ "github.com/gin-gonic/gin"
+ "github.com/gorilla/sessions"
+)
+
+var store = sessions.NewCookieStore([]byte("f0q0qew0!)§(ds9713lsda231"))
+
+const LoginSessionName = "session"
+
+func GetUsername(c *gin.Context) (string, bool) {
+ session, _ := getSession(c)
+ username, ok := session.Values["username"].(string)
+ return username, ok
+}
+
+func GetAuthenticated(c *gin.Context) (bool, bool) {
+ session, _ := getSession(c)
+ auth, ok := session.Values["authenticated"].(bool)
+ return auth, ok
+}
+
+func getSession(c *gin.Context) (*sessions.Session, error) {
+ return store.Get(c.Request, LoginSessionName)
+}
+
+func InvalidateSession(c *gin.Context) error {
+ session, _ := getSession(c)
+ session.Options.MaxAge = -1
+ err := session.Save(c.Request, c.Writer)
+
+ return err
+}
+
+func SetLoginSession(username string, c *gin.Context) error {
+ session, _ := getSession(c)
+ session.Values["authenticated"] = true
+ session.Values["username"] = username
+ err := session.Save(c.Request, c.Writer)
+
+ return err
+}
diff --git a/main.go b/main.go
index 0e66da0..0347382 100644
--- a/main.go
+++ b/main.go
@@ -53,10 +53,14 @@ func init() {
if err != nil {
log.Fatal(err)
}
+
+ controllers.LoadMetrics()
}
func main() {
- //gin.SetMode(gin.ReleaseMode) // uncomment upon release
+ if os.Getenv("GO_ENV") == "production" {
+ gin.SetMode(gin.ReleaseMode)
+ }
router := gin.New()
err := router.SetTrustedProxies([]string{"127.0.0.1"})
@@ -72,6 +76,13 @@ func main() {
models.ConnectDatabase()
models.ConnectCache()
+ var code models.ActivationCode
+ if err := models.DB.First(&code).Error; err != nil {
+ firstCode := utils.GenerateActivationCode()
+ models.DB.Create(&models.ActivationCode{Code: firstCode, UserRole: models.AdminRole})
+ log.Println("Created first activation code with ADMIN role:\n" + firstCode)
+ }
+
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(
@@ -114,6 +125,8 @@ func main() {
protected.GET("/cache/:player_id", controllers.GetCacheByPlayerID)
+ protected.GET("/score/:player_id", controllers.GetScoreByPlayerID)
+
protected.GET("/game", controllers.GetGames)
protected.GET("/game_html", controllers.GetGamesHTML)
diff --git a/models/cache.go b/models/cache.go
deleted file mode 100644
index a00fa55..0000000
--- a/models/cache.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package models
-
-import "time"
-
-type PlayerCache struct {
- CacheID uint `json:"cache_id" gorm:"primary_key"`
- PlayerID uint `json:"player_id"`
- CacheDate time.Time `json:"date"`
- Score float32 `json:"score" gorm:"default:-1.0"`
- Game string `json:"game"`
-}
diff --git a/models/metric_setting.go b/models/metric_setting.go
deleted file mode 100644
index 3ccd374..0000000
--- a/models/metric_setting.go
+++ /dev/null
@@ -1,9 +0,0 @@
-package models
-
-type MetricSetting struct {
- ID uint `json:"id" gorm:"primary_key"`
- Name string `json:"name" binding:"required"`
- WeaponCategory string `json:"weapon_category" binding:"required"`
- Value float64 `json:"value" binding:"required"`
- Game string `json:"game" binding:"required"`
-}
diff --git a/models/metrics_json.go b/models/metrics_json.go
new file mode 100644
index 0000000..b486cbd
--- /dev/null
+++ b/models/metrics_json.go
@@ -0,0 +1,18 @@
+package models
+
+type GameMetrics struct {
+ GameMetrics []GameMetric
+}
+
+type GameMetric struct {
+ NormalizeFactor float64
+ TopWeaponCount int
+ GameName string
+ WeaponMetrics []WeaponMetric
+}
+
+type WeaponMetric struct {
+ WeaponCategory string
+ AccuracyFactor float64
+ KpmFactor float64
+}
diff --git a/models/setup.go b/models/setup.go
index 0ff3f48..3340fc7 100644
--- a/models/setup.go
+++ b/models/setup.go
@@ -1,13 +1,13 @@
package models
import (
- "InfantrySkillCalculator/utils"
"context"
+ "github.com/glebarez/sqlite"
"github.com/redis/go-redis/v9"
- "gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
+ "os"
)
var DB *gorm.DB
@@ -33,10 +33,6 @@ func ConnectDatabase() {
if err != nil {
log.Fatal(err)
}
- err = database.AutoMigrate(&PlayerCache{})
- if err != nil {
- log.Fatal(err)
- }
err = database.AutoMigrate(&User{})
if err != nil {
log.Fatal(err)
@@ -44,13 +40,6 @@ func ConnectDatabase() {
err = database.AutoMigrate(&ActivationCode{})
if err != nil {
log.Fatal(err)
- } else {
- var code ActivationCode
- if err := database.First(&code).Error; err != nil {
- firstCode := utils.GenerateActivationCode()
- database.Create(&ActivationCode{Code: firstCode, UserRole: AdminRole})
- log.Println("Created first activation code with ADMIN role:\n" + firstCode)
- }
}
err = database.AutoMigrate(&Game{})
if err != nil {
@@ -64,10 +53,6 @@ func ConnectDatabase() {
}
}
- err = database.AutoMigrate(&MetricSetting{})
- if err != nil {
- log.Fatal(err)
- }
err = database.AutoMigrate(&UserSettings{})
if err != nil {
log.Fatal(err)
@@ -77,8 +62,17 @@ func ConnectDatabase() {
}
func ConnectCache() {
+ address := os.Getenv("REDIS_ADDRESS")
+ if address == "" {
+ address = "127.0.0.1"
+ }
+ port := os.Getenv("REDIS_PORT")
+ if port == "" {
+ port = "6379"
+ }
+
Cache = redis.NewClient(&redis.Options{
- Addr: "127.0.0.1:6379",
+ Addr: address + ":" + port,
Password: "",
DB: 0,
})
diff --git a/models/tracker_json.go b/models/tracker_json.go
new file mode 100644
index 0000000..4e9ec9d
--- /dev/null
+++ b/models/tracker_json.go
@@ -0,0 +1,16 @@
+package models
+
+type TrackerWeaponJSON struct {
+ Weapons []Weapon `json:"weapons"`
+}
+
+type Weapon struct {
+ Type string `json:"type"`
+ Name string `json:"weaponName"`
+ ID string `json:"id"`
+ Kills int `json:"kills"`
+ KPM float64 `json:"killsPerMinute"`
+ ShotsFired int `json:"shotsFired"`
+ ShotsHit int `json:"shotsHit"`
+ Accuracy float64 `json:"-"`
+}
diff --git a/pages.go b/pages.go
index 5a11653..60e10cc 100644
--- a/pages.go
+++ b/pages.go
@@ -2,10 +2,10 @@ package main
import (
"InfantrySkillCalculator/controllers"
- "InfantrySkillCalculator/utils"
"github.com/gin-gonic/gin"
"log"
"net/http"
+ "session"
)
func mainPage(c *gin.Context) {
@@ -20,9 +20,7 @@ func mainPage(c *gin.Context) {
}
func loginPage(c *gin.Context) {
- session, _ := utils.Store.Get(c.Request, utils.LoginSessionName)
-
- if auth, ok := session.Values["authenticated"].(bool); ok && auth {
+ if auth, ok := session.GetAuthenticated(c); ok && auth {
c.Redirect(http.StatusFound, "/")
return
}
@@ -42,11 +40,7 @@ func loginPost(c *gin.Context) {
return
}
- session, _ := utils.Store.Get(c.Request, utils.LoginSessionName)
- session.Values["authenticated"] = true
- session.Values["username"] = username
- err := session.Save(c.Request, c.Writer)
- if err != nil {
+ if err := session.SetLoginSession(username, c); err != nil {
c.JSON(http.StatusInternalServerError, nil)
return
}
@@ -56,10 +50,7 @@ func loginPost(c *gin.Context) {
}
func logout(c *gin.Context) {
- session, _ := utils.Store.Get(c.Request, utils.LoginSessionName)
- session.Values["authenticated"] = false
- err := session.Save(c.Request, c.Writer)
- if err != nil {
+ if err := session.InvalidateSession(c); err != nil {
c.JSON(http.StatusInternalServerError, nil)
return
}
@@ -68,9 +59,7 @@ func logout(c *gin.Context) {
}
func registerPage(c *gin.Context) {
- session, _ := utils.Store.Get(c.Request, utils.LoginSessionName)
-
- if auth, ok := session.Values["authenticated"].(bool); ok && auth {
+ if auth, ok := session.GetAuthenticated(c); ok && auth {
c.Redirect(http.StatusFound, "/")
return
}
@@ -99,11 +88,7 @@ func registerPost(c *gin.Context) {
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)
- if err != nil {
+ if err := session.SetLoginSession(username, c); err != nil {
c.JSON(http.StatusInternalServerError, nil)
return
}
diff --git a/static/index.css b/static/index.css
index d6c9eb0..7e014bc 100644
--- a/static/index.css
+++ b/static/index.css
@@ -51,5 +51,4 @@ body, html {
.form-control.overflow-auto {
height: auto; /* Adjust as needed */
max-height: 300px; /* Adjust as needed */
-}
-
+}
\ No newline at end of file
diff --git a/static/index.js b/static/index.js
index d51792e..4c6bc8d 100644
--- a/static/index.js
+++ b/static/index.js
@@ -1,5 +1,5 @@
document.addEventListener('DOMContentLoaded', function() {
- const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
+ const tooltipTriggerList = document.querySelectorAll('[config-bs-toggle="tooltip"]');
tooltipTriggerList.forEach((elem) => {
new bootstrap.Tooltip(elem);
});
@@ -180,3 +180,13 @@ function createCodeDialog(btn) {
}
});
}
+
+function singleCalcSpinner(sender) {
+ const spinner = '';
+ const score = sender.previousElementSibling.children[1];
+ score.innerHTML = spinner;
+ sender.disabled = true;
+ sender.addEventListener('htmx:afterRequest', function () {
+ sender.disabled = false;
+ }, {once: true});
+}
\ No newline at end of file
diff --git a/templates/components/bottom_controls.html b/templates/components/bottom_controls.html
index 78f8d47..18ca1aa 100644
--- a/templates/components/bottom_controls.html
+++ b/templates/components/bottom_controls.html
@@ -61,13 +61,13 @@
-
-
+
Einzel-Abfrage
diff --git a/templates/modals/add_clan.html b/templates/modals/add_clan.html
index 4eb1b1c..427e9d8 100644
--- a/templates/modals/add_clan.html
+++ b/templates/modals/add_clan.html
@@ -86,7 +86,7 @@
}
function createSubmitClanHandler(modalEvent) {
- return function submitClanHandler(e) {
+ return function submitClanHandler(_) {
const [clanList, otherClanList] = getClanLists(modalEvent);
if (!validateInputs()) {
@@ -130,7 +130,7 @@
submitButton.addEventListener('click', submitClanHandler);
});
- addClanModal.addEventListener('hidden.bs.modal', event => {
+ addClanModal.addEventListener('hidden.bs.modal', _ => {
submitButton.removeEventListener('click', submitClanHandler);
clanName.value = "";
diff --git a/templates/modals/add_player.html b/templates/modals/add_player.html
index d7dc73d..bcced30 100644
--- a/templates/modals/add_player.html
+++ b/templates/modals/add_player.html
@@ -38,8 +38,8 @@
const playerName = addPlayerModal.querySelector('#playerName');
const clanName = addPlayerModal.querySelector('#playerClanName');
const errorDiv = addPlayerModal.querySelector('.error-message');
- const homeClanListIndex = document.getElementById('home-clan').selectedIndex;
- const oppClanListIndex = document.getElementById('opponent-clan').selectedIndex;
+ const homeClanList = document.getElementById('home-clan');
+ const oppClanList = document.getElementById('opponent-clan');
function validateInput() {
if (playerName.value.length < 1) {
@@ -50,8 +50,8 @@
return true;
}
- function createSubmitPlayerHandler(modalEvent) {
- return function submitPlayerHandler(e) {
+ function createSubmitPlayerHandler(_) {
+ return function submitPlayerHandler(_) {
if (!validateInput())
return;
@@ -74,7 +74,7 @@
return response.text();
})
.then(() => {
- const sameClan = homeClanListIndex === oppClanListIndex;
+ const sameClan = homeClanList.selectedIndex === oppClanList.selectedIndex;
if (playerList.id === 'home-player-list' || sameClan)
htmx.ajax('GET', '/players_html', {target: '#home-player-list', values: {"clan_id": getSelectedClanId("home-clan")}});
if (playerList.id === 'opponent-player-list' || sameClan)
@@ -98,7 +98,7 @@
clanName.value = selectedClan.innerText;
});
- addPlayerModal.addEventListener('hidden.bs.modal', event => {
+ addPlayerModal.addEventListener('hidden.bs.modal', _ => {
submitButton.removeEventListener('click', submitPlayerHandler);
playerName.value = "";
diff --git a/templates/modals/delete_clan.html b/templates/modals/delete_clan.html
index db4d39c..fcc5ae0 100644
--- a/templates/modals/delete_clan.html
+++ b/templates/modals/delete_clan.html
@@ -24,6 +24,7 @@
document.addEventListener('DOMContentLoaded', function() {
const deleteClanModal = document.getElementById('deleteClanModal')
const deleteClanModalBS = new bootstrap.Modal('#deleteClanModal');
+
if (deleteClanModal) {
deleteClanModal.addEventListener('show.bs.modal', event => {
const [clanList, otherClanList] = getClanLists(event);
diff --git a/templates/modals/delete_player.html b/templates/modals/delete_player.html
index a3fe526..bbfc67b 100644
--- a/templates/modals/delete_player.html
+++ b/templates/modals/delete_player.html
@@ -34,8 +34,8 @@
modalBodyInput.innerText = selectedPlayer;
const playerListId = button.closest('ul').parentElement.parentElement.id;
- const homeClanListIndex = document.getElementById('home-clan').selectedIndex;
- const oppClanListIndex = document.getElementById('opponent-clan').selectedIndex;
+ const homeClanList = document.getElementById('home-clan');
+ const oppClanList = document.getElementById('opponent-clan');
const submitButton = deletePlayerModal.querySelector('button[name="submit"]');
submitButton.addEventListener('click', function () {
@@ -46,7 +46,7 @@
}
})
.then(() => {
- const sameClan = homeClanListIndex === oppClanListIndex;
+ const sameClan = homeClanList.selectedIndex === oppClanList.selectedIndex;
if (playerListId === 'home-player-list' || sameClan)
htmx.ajax('GET', '/players_html', {target: '#home-player-list', values: {"clan_id": getSelectedClanId("home-clan")}});
if (playerListId === 'opponent-player-list' || sameClan)
diff --git a/templates/modals/edit_clan.html b/templates/modals/edit_clan.html
index ab1b819..dbc584d 100644
--- a/templates/modals/edit_clan.html
+++ b/templates/modals/edit_clan.html
@@ -87,7 +87,7 @@
}
function createSubmitClanHandler() {
- return function submitClanHandler(e) {
+ return function submitClanHandler(_) {
if (!validateInputs())
return;
@@ -149,7 +149,7 @@
});
});
- editClanModal.addEventListener('hidden.bs.modal', event => {
+ editClanModal.addEventListener('hidden.bs.modal', _ => {
submitButton.removeEventListener('click', submitClanHandler);
clanName.value = "";
diff --git a/templates/modals/edit_player.html b/templates/modals/edit_player.html
index 42a65b1..1633738 100644
--- a/templates/modals/edit_player.html
+++ b/templates/modals/edit_player.html
@@ -34,8 +34,8 @@
const submitButton = editPlayerModal.querySelector('button[name="submit"]');
const playerName = editPlayerModal.querySelector('#editPlayerName');
const errorDiv = editPlayerModal.querySelector('.error-message');
- const homeClanListIndex = document.getElementById('home-clan').selectedIndex;
- const oppClanListIndex = document.getElementById('opponent-clan').selectedIndex;
+ const homeClanList = document.getElementById('home-clan');
+ const oppClanList = document.getElementById('opponent-clan');
function validateInput() {
if (playerName.value.length < 1) {
@@ -71,7 +71,7 @@
return response.text();
})
.then(() => {
- const sameClan = homeClanListIndex === oppClanListIndex;
+ const sameClan = homeClanList.selectedIndex === oppClanList.selectedIndex;
if (playerList.id === 'home-player-list' || sameClan)
htmx.ajax('GET', '/players_html', {target: '#home-player-list', values: {"clan_id": getSelectedClanId("home-clan")}});
if (playerList.id === 'opponent-player-list' || sameClan)
@@ -95,7 +95,7 @@
playerName.value = event.relatedTarget.closest('.input-group').querySelector('span').innerText;
});
- editPlayerModal.addEventListener('hide.bs.modal', event => {
+ editPlayerModal.addEventListener('hide.bs.modal', _ => {
submitButton.removeEventListener('click', submitPlayerHandler);
playerName.value = "";
diff --git a/templates/modals/settings.html b/templates/modals/settings.html
index 534a451..e650efb 100644
--- a/templates/modals/settings.html
+++ b/templates/modals/settings.html
@@ -53,7 +53,7 @@
const useCache = document.getElementById('settingsUseCache');
function createSubmitSettingsHandler() {
- return function submitSettingsHandler(e) {
+ return function submitSettingsHandler(_) {
let activeGameId = games.options[games.selectedIndex].value;
if (activeGameId === '') {
alert('Bitte wähle ein Spiel aus.');
@@ -88,7 +88,7 @@
}
if (settingsModal) {
- settingsModal.addEventListener('show.bs.modal', event => {
+ settingsModal.addEventListener('show.bs.modal', _ => {
submitSettingsHandler = createSubmitSettingsHandler();
submitButton.addEventListener('click', submitSettingsHandler);
diff --git a/templates/player_list_item.html b/templates/player_list_item.html
index 24264ee..688c4c6 100644
--- a/templates/player_list_item.html
+++ b/templates/player_list_item.html
@@ -3,10 +3,10 @@
%s
-
- %s
-
-
+
+ %s
+
+
diff --git a/utils/globals.go b/utils/globals.go
index f9c11a9..0255223 100644
--- a/utils/globals.go
+++ b/utils/globals.go
@@ -1,11 +1,11 @@
package utils
import (
+ "InfantrySkillCalculator/models"
"io"
"time"
)
-var ClanLastChanged time.Time = time.Now().UTC()
-var CacheLastChanged time.Time = time.Now().UTC()
-var PlayersLastChanged time.Time = time.Now().UTC()
var GinWriter io.Writer = nil
+var GameMetrics models.GameMetrics
+var PlayerCacheLifetime = 24 * time.Hour
diff --git a/utils/score.go b/utils/score.go
new file mode 100644
index 0000000..2863a8f
--- /dev/null
+++ b/utils/score.go
@@ -0,0 +1,129 @@
+package utils
+
+import (
+ "InfantrySkillCalculator/models"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "math"
+ "net/http"
+ "sort"
+)
+
+func GetGameMetric(gameTag string) *models.GameMetric {
+ for _, metric := range GameMetrics.GameMetrics {
+ if metric.GameName == gameTag {
+ return &metric
+ }
+ }
+ return nil
+}
+
+func FindWeaponMetric(weaponMetrics []models.WeaponMetric, weaponCategory string) *models.WeaponMetric {
+ for _, metric := range weaponMetrics {
+ if metric.WeaponCategory == weaponCategory {
+ return &metric
+ }
+ }
+ return nil
+}
+
+func CalcPlayerScore(playerName string, gameTag string) float32 {
+ if gameTag != "BF5" && gameTag != "BF2042" {
+ _, _ = fmt.Fprintf(GinWriter, "UNSUPPORTED GAME: "+gameTag+"\n")
+ return -1
+ }
+
+ gameMetrics := GetGameMetric(gameTag)
+ if gameMetrics == nil {
+ _, _ = fmt.Fprintf(GinWriter, "No game metrics specified for '"+gameTag+"'\n")
+ return -1
+ }
+
+ normalizeFactor := gameMetrics.NormalizeFactor
+ topWeaponCount := gameMetrics.TopWeaponCount
+
+ c := http.Client{}
+
+ var reqUri string
+ if gameTag == "BF5" {
+ reqUri = "https://api.gametools.network/bfv/weapons/?raw=false&format_values=false&name=" + playerName + "&platform=pc"
+ } else if gameTag == "BF2042" {
+ reqUri = "https://api.gametools.network/bf2042/stats/?raw=false&format_values=false&name=" + playerName + "&platform=pc"
+ }
+
+ req, err := http.NewRequest("GET", reqUri, nil)
+ if err != nil {
+ _, _ = fmt.Fprintf(GinWriter, err.Error()+"\n")
+ return -1
+ }
+
+ req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36")
+
+ res, err := c.Do(req)
+ if err != nil {
+ _, _ = fmt.Fprintf(GinWriter, err.Error()+"\n")
+ return -1
+ }
+
+ defer func(Body io.ReadCloser) {
+ _ = Body.Close()
+ }(res.Body)
+ if res.StatusCode == 404 {
+ _, _ = fmt.Fprintf(GinWriter, "User '"+playerName+"' does not exist!\n")
+ return -1
+ } else if res.StatusCode != 200 {
+ log.Fatalf("Status code error: %d %s", res.StatusCode, res.Status)
+ }
+
+ var data models.TrackerWeaponJSON
+ var body, _ = io.ReadAll(res.Body)
+ if err := json.Unmarshal(body, &data); err != nil {
+ log.Fatalf("Failed to deserialize tracker API response: %s", err)
+ }
+
+ sort.SliceStable(data.Weapons, func(i, j int) bool { return data.Weapons[i].Kills > data.Weapons[j].Kills })
+ var top []models.Weapon
+
+ for _, weapon := range data.Weapons {
+ if len(top) >= topWeaponCount {
+ break
+ }
+ if gameTag == "BF2042" && weapon.Kills < 100 {
+ break
+ }
+
+ acc := (float64(weapon.ShotsHit) / float64(weapon.ShotsFired)) * 100
+ kpm := weapon.KPM
+
+ weaponMetrics := FindWeaponMetric(gameMetrics.WeaponMetrics, weapon.Type)
+ if weaponMetrics == nil {
+ _, _ = fmt.Fprintf(GinWriter, "No weapon metrics specified for '"+gameTag+"', WType '"+weapon.Type+"'\n")
+ continue
+ }
+
+ accFactor := weaponMetrics.AccuracyFactor
+ kpmFactor := weaponMetrics.KpmFactor
+
+ acc /= accFactor
+ kpm /= kpmFactor
+ weapon.Accuracy = acc
+ weapon.KPM = kpm
+
+ top = append(top, weapon)
+ }
+
+ sumAcc := 0.0
+ sumKpm := 0.0
+
+ for _, w := range top {
+ sumAcc += w.Accuracy
+ sumKpm += w.KPM
+ }
+ accAvg := sumAcc / float64(len(top))
+ kpmAvg := sumKpm / float64(len(top))
+ score := math.Round(((accAvg*kpmAvg)/normalizeFactor)*100) / 100
+
+ return float32(score)
+}
diff --git a/utils/session.go b/utils/session.go
deleted file mode 100644
index e71c00a..0000000
--- a/utils/session.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package utils
-
-import "github.com/gorilla/sessions"
-
-var Store = sessions.NewCookieStore([]byte("f0q0qew0!)§(ds9713lsda231"))
-
-const LoginSessionName = "session"