diff --git a/Dockerfile b/Dockerfile index fe96d36..cc0f185 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,9 @@ 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 . +RUN CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-w -s" -o isc ./cmd FROM alpine:latest diff --git a/auth.go b/cmd/auth.go similarity index 100% rename from auth.go rename to cmd/auth.go diff --git a/main.go b/cmd/main.go similarity index 98% rename from main.go rename to cmd/main.go index 716ab3c..b33841d 100644 --- a/main.go +++ b/cmd/main.go @@ -79,7 +79,7 @@ func main() { admin.Use(AdminAuthRequired()) models.ConnectDatabase() - models.ConnectCache() + models.ConnectCache(utils.PlayerCacheLifetime) var code models.ActivationCode if err := models.DB.First(&code).Error; err != nil { diff --git a/pages.go b/cmd/pages.go similarity index 100% rename from pages.go rename to cmd/pages.go diff --git a/controllers/cache_controller.go b/controllers/cache_controller.go index b6a5ed6..2743545 100644 --- a/controllers/cache_controller.go +++ b/controllers/cache_controller.go @@ -5,7 +5,6 @@ import ( "InfantrySkillCalculator/utils" "fmt" "github.com/gin-gonic/gin" - "internal/cache" "log" "net/http" ) @@ -30,7 +29,7 @@ func GetCacheByPlayerID(c *gin.Context) { return } - val, err := cache.GetScore(playerId, game.Tag) + val, err := models.PlayerCache.GetScore(playerId, game.Tag) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"}) } else { @@ -52,7 +51,7 @@ func AddCache(c *gin.Context) { return } - err := cache.SetScore(input.PlayerID, input.GameTag, input.Score) + err := models.PlayerCache.SetScore(input.PlayerID, input.GameTag, input.Score) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()}) return @@ -67,7 +66,7 @@ func UpdateCacheByPlayerID(c *gin.Context) { gameTag := c.Request.URL.Query().Get("game") score := utils.StringToFloat(c.PostForm("score")) - err := cache.SetScore(playerID, gameTag, score) + err := models.PlayerCache.SetScore(playerID, gameTag, score) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()}) } else { @@ -80,7 +79,7 @@ func DeleteCacheByPlayerID(c *gin.Context) { playerID := utils.StringToUint(c.Param("id")) gameTag := c.Request.URL.Query().Get("game") - if err := cache.DeleteScore(playerID, gameTag); err != nil { + if err := models.PlayerCache.DeleteScore(playerID, gameTag); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) } else { c.JSON(http.StatusOK, nil) @@ -89,7 +88,7 @@ func DeleteCacheByPlayerID(c *gin.Context) { // DeleteAllCaches DELETE /cache func DeleteAllCaches(c *gin.Context) { - if err := cache.PurgeCache(); err != nil { + if err := models.PlayerCache.PurgeCache(); err != nil { c.String(http.StatusBadRequest, err.Error()) } else { c.String(http.StatusOK, "Purged all caches!") @@ -116,11 +115,11 @@ func GetScoreByPlayerID(c *gin.Context) { return } - score, err := cache.GetScore(player.ID, game.Tag) + score, err := models.PlayerCache.GetScore(player.ID, game.Tag) if err != nil || score == -1 { score = utils.CalcPlayerScore(player.Name, game.Tag) if score == score && score != -1 { // not NaN - if err := cache.SetScore(player.ID, game.Tag, score); err != nil { + if err := models.PlayerCache.SetScore(player.ID, game.Tag, score); err != nil { log.Fatal(err) } } diff --git a/controllers/player_controller.go b/controllers/player_controller.go index 140cea7..9a0c733 100644 --- a/controllers/player_controller.go +++ b/controllers/player_controller.go @@ -8,7 +8,6 @@ import ( "gorm.io/gorm" "gorm.io/gorm/clause" "html/template" - "internal/cache" "log" "net/http" ) @@ -51,7 +50,12 @@ func GetPlayersByClanHTML(c *gin.Context) { return } - scores, err := cache.GetScores(players, game.Tag) + var playerIDs []uint + for _, player := range players { + playerIDs = append(playerIDs, player.ID) + } + + scores, err := models.PlayerCache.GetScores(playerIDs, game.Tag) if err != nil { c.String(http.StatusBadRequest, "") log.Fatal(err) diff --git a/docker-compose.yml b/docker-compose.yml index 6b3e52d..97f0086 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,7 @@ services: - "127.0.0.1:8000:8000" environment: - GO_ENV=production - - REDIS_ADDRESS=redis + - REDIS_ADDRESS=redis:6379 depends_on: - redis volumes: diff --git a/internal/cache/cache.go b/internal/cache/cache.go index 6a45bd2..caf4b12 100644 --- a/internal/cache/cache.go +++ b/internal/cache/cache.go @@ -1,18 +1,40 @@ package cache import ( - "InfantrySkillCalculator/models" - "InfantrySkillCalculator/utils" "context" "errors" "fmt" "github.com/redis/go-redis/v9" + "strconv" + "time" ) var ctx = context.Background() -func GetValue(key string) (string, error) { - val, err := models.Cache.Get(ctx, key).Result() +type PlayerCache struct { + cache *redis.Client + lifetime time.Duration +} + +func NewPlayerCache(address string, lifetime time.Duration) *PlayerCache { + return &PlayerCache{ + redis.NewClient( + &redis.Options{ + Addr: address, + Password: "", + DB: 0, + }), + lifetime, + } +} + +func (pc *PlayerCache) Connect() error { + _, err := pc.cache.Ping(ctx).Result() + return err +} + +func (pc *PlayerCache) GetValue(key string) (string, error) { + val, err := pc.cache.Get(ctx, key).Result() if err != nil { return "", err // cache miss or error } else { @@ -20,31 +42,32 @@ func GetValue(key string) (string, error) { } } -func SetValue(key string, value interface{}) error { - return models.Cache.Set(ctx, key, value, utils.PlayerCacheLifetime).Err() +func (pc *PlayerCache) SetValue(key string, value interface{}) error { + return pc.cache.Set(ctx, key, value, pc.lifetime).Err() } -func SetScore(playerId uint, gameTag string, score float32) error { - key := GetPlayerCacheKey(playerId, gameTag) - return SetValue(key, score) +func (pc *PlayerCache) SetScore(playerId uint, gameTag string, score float32) error { + key := getPlayerCacheKey(playerId, gameTag) + return pc.SetValue(key, score) } -func GetScore(playerId uint, gameTag string) (float32, error) { - key := GetPlayerCacheKey(playerId, gameTag) - val, err := GetValue(key) +func (pc *PlayerCache) GetScore(playerId uint, gameTag string) (float32, error) { + key := getPlayerCacheKey(playerId, gameTag) + val, err := pc.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 + valFloat, _ := strconv.ParseFloat(val, 32) + return float32(valFloat), nil // cache hit } } -func GetScores(players []models.Player, gameTag string) ([]float32, error) { - vals, err := models.Cache.Pipelined(ctx, func(pipe redis.Pipeliner) error { - for _, p := range players { - key := GetPlayerCacheKey(p.ID, gameTag) +func (pc *PlayerCache) GetScores(playerIds []uint, gameTag string) ([]float32, error) { + vals, err := pc.cache.Pipelined(ctx, func(pipe redis.Pipeliner) error { + for _, id := range playerIds { + key := getPlayerCacheKey(id, gameTag) pipe.Get(ctx, key) } return nil @@ -69,15 +92,15 @@ func GetScores(players []models.Player, gameTag string) ([]float32, error) { return scores, nil } -func DeleteScore(playerId uint, gameTag string) error { - key := GetPlayerCacheKey(playerId, gameTag) - return models.Cache.Del(ctx, key).Err() +func (pc *PlayerCache) DeleteScore(playerId uint, gameTag string) error { + key := getPlayerCacheKey(playerId, gameTag) + return pc.cache.Del(ctx, key).Err() } -func PurgeCache() error { - return models.Cache.FlushAll(ctx).Err() +func (pc *PlayerCache) PurgeCache() error { + return pc.cache.FlushAll(ctx).Err() } -func GetPlayerCacheKey(playerId uint, gameTag string) string { +func getPlayerCacheKey(playerId uint, gameTag string) string { return fmt.Sprintf("player:%d:game:%s", playerId, gameTag) } diff --git a/models/setup.go b/models/setup.go index 3340fc7..5843482 100644 --- a/models/setup.go +++ b/models/setup.go @@ -1,18 +1,17 @@ package models import ( - "context" + "cache" "github.com/glebarez/sqlite" - "github.com/redis/go-redis/v9" "gorm.io/gorm" "gorm.io/gorm/logger" "log" "os" + "time" ) var DB *gorm.DB -var ctx = context.Background() -var Cache *redis.Client +var PlayerCache *cache.PlayerCache func ConnectDatabase() { database, err := gorm.Open(sqlite.Open("isc_data.db"), &gorm.Config{ @@ -61,25 +60,15 @@ func ConnectDatabase() { DB = database } -func ConnectCache() { +func ConnectCache(playerCacheLifetime time.Duration) { address := os.Getenv("REDIS_ADDRESS") if address == "" { - address = "127.0.0.1" - } - port := os.Getenv("REDIS_PORT") - if port == "" { - port = "6379" + address = "127.0.0.1:6379" } - Cache = redis.NewClient(&redis.Options{ - Addr: address + ":" + port, - Password: "", - DB: 0, - }) - - _, err := Cache.Ping(ctx).Result() - if err != nil { - Cache = nil + PlayerCache = cache.NewPlayerCache(address, playerCacheLifetime) + if err := PlayerCache.Connect(); err != nil { + PlayerCache = nil log.Fatal("Failed to connect to Redis! " + err.Error()) } } diff --git a/templates/player_list_item.html b/templates/player_list_item.html index 5c4b763..b76342e 100644 --- a/templates/player_list_item.html +++ b/templates/player_list_item.html @@ -1,7 +1,9 @@ {{ range . }}