Player cache and project structure refactor.
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
69
internal/cache/cache.go
vendored
69
internal/cache/cache.go
vendored
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{{ range . }}
|
||||
<div class="input-group input-group-lg mb-1">
|
||||
<div class="input-group-text py-1 px-2">
|
||||
<label>
|
||||
<input class="form-check-input fs-4 border-secondary mt-0" type="checkbox" value="" onchange="updateSelectedPlayers(this)">
|
||||
</label>
|
||||
</div>
|
||||
<span class="form-control py-2 px-3" style="width: 10em">{{ .PlayerName }}</span>
|
||||
<div class="form-control text-center px-2 text-secondary">
|
||||
|
||||
Reference in New Issue
Block a user