Player cache and project structure refactor.
This commit is contained in:
@@ -2,13 +2,9 @@ FROM golang:1.21-alpine AS builder
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY go.mod go.sum internal/**/go.mod ./
|
|
||||||
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
COPY . .
|
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
|
FROM alpine:latest
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ func main() {
|
|||||||
admin.Use(AdminAuthRequired())
|
admin.Use(AdminAuthRequired())
|
||||||
|
|
||||||
models.ConnectDatabase()
|
models.ConnectDatabase()
|
||||||
models.ConnectCache()
|
models.ConnectCache(utils.PlayerCacheLifetime)
|
||||||
|
|
||||||
var code models.ActivationCode
|
var code models.ActivationCode
|
||||||
if err := models.DB.First(&code).Error; err != nil {
|
if err := models.DB.First(&code).Error; err != nil {
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"InfantrySkillCalculator/utils"
|
"InfantrySkillCalculator/utils"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"internal/cache"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@@ -30,7 +29,7 @@ func GetCacheByPlayerID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err := cache.GetScore(playerId, game.Tag)
|
val, err := models.PlayerCache.GetScore(playerId, game.Tag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Record not found!"})
|
||||||
} else {
|
} else {
|
||||||
@@ -52,7 +51,7 @@ func AddCache(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := cache.SetScore(input.PlayerID, input.GameTag, input.Score)
|
err := models.PlayerCache.SetScore(input.PlayerID, input.GameTag, input.Score)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()})
|
||||||
return
|
return
|
||||||
@@ -67,7 +66,7 @@ func UpdateCacheByPlayerID(c *gin.Context) {
|
|||||||
gameTag := c.Request.URL.Query().Get("game")
|
gameTag := c.Request.URL.Query().Get("game")
|
||||||
score := utils.StringToFloat(c.PostForm("score"))
|
score := utils.StringToFloat(c.PostForm("score"))
|
||||||
|
|
||||||
err := cache.SetScore(playerID, gameTag, score)
|
err := models.PlayerCache.SetScore(playerID, gameTag, score)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Cache update failed! Error: " + err.Error()})
|
||||||
} else {
|
} else {
|
||||||
@@ -80,7 +79,7 @@ func DeleteCacheByPlayerID(c *gin.Context) {
|
|||||||
playerID := utils.StringToUint(c.Param("id"))
|
playerID := utils.StringToUint(c.Param("id"))
|
||||||
gameTag := c.Request.URL.Query().Get("game")
|
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()})
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusOK, nil)
|
c.JSON(http.StatusOK, nil)
|
||||||
@@ -89,7 +88,7 @@ func DeleteCacheByPlayerID(c *gin.Context) {
|
|||||||
|
|
||||||
// DeleteAllCaches DELETE /cache
|
// DeleteAllCaches DELETE /cache
|
||||||
func DeleteAllCaches(c *gin.Context) {
|
func DeleteAllCaches(c *gin.Context) {
|
||||||
if err := cache.PurgeCache(); err != nil {
|
if err := models.PlayerCache.PurgeCache(); err != nil {
|
||||||
c.String(http.StatusBadRequest, err.Error())
|
c.String(http.StatusBadRequest, err.Error())
|
||||||
} else {
|
} else {
|
||||||
c.String(http.StatusOK, "Purged all caches!")
|
c.String(http.StatusOK, "Purged all caches!")
|
||||||
@@ -116,11 +115,11 @@ func GetScoreByPlayerID(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
score, err := cache.GetScore(player.ID, game.Tag)
|
score, err := models.PlayerCache.GetScore(player.ID, game.Tag)
|
||||||
if err != nil || score == -1 {
|
if err != nil || score == -1 {
|
||||||
score = utils.CalcPlayerScore(player.Name, game.Tag)
|
score = utils.CalcPlayerScore(player.Name, game.Tag)
|
||||||
if score == score && score != -1 { // not NaN
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
"html/template"
|
"html/template"
|
||||||
"internal/cache"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@@ -51,7 +50,12 @@ func GetPlayersByClanHTML(c *gin.Context) {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
c.String(http.StatusBadRequest, "")
|
c.String(http.StatusBadRequest, "")
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ services:
|
|||||||
- "127.0.0.1:8000:8000"
|
- "127.0.0.1:8000:8000"
|
||||||
environment:
|
environment:
|
||||||
- GO_ENV=production
|
- GO_ENV=production
|
||||||
- REDIS_ADDRESS=redis
|
- REDIS_ADDRESS=redis:6379
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
69
internal/cache/cache.go
vendored
69
internal/cache/cache.go
vendored
@@ -1,18 +1,40 @@
|
|||||||
package cache
|
package cache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"InfantrySkillCalculator/models"
|
|
||||||
"InfantrySkillCalculator/utils"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ctx = context.Background()
|
var ctx = context.Background()
|
||||||
|
|
||||||
func GetValue(key string) (string, error) {
|
type PlayerCache struct {
|
||||||
val, err := models.Cache.Get(ctx, key).Result()
|
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 {
|
if err != nil {
|
||||||
return "", err // cache miss or error
|
return "", err // cache miss or error
|
||||||
} else {
|
} else {
|
||||||
@@ -20,31 +42,32 @@ func GetValue(key string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetValue(key string, value interface{}) error {
|
func (pc *PlayerCache) SetValue(key string, value interface{}) error {
|
||||||
return models.Cache.Set(ctx, key, value, utils.PlayerCacheLifetime).Err()
|
return pc.cache.Set(ctx, key, value, pc.lifetime).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetScore(playerId uint, gameTag string, score float32) error {
|
func (pc *PlayerCache) SetScore(playerId uint, gameTag string, score float32) error {
|
||||||
key := GetPlayerCacheKey(playerId, gameTag)
|
key := getPlayerCacheKey(playerId, gameTag)
|
||||||
return SetValue(key, score)
|
return pc.SetValue(key, score)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetScore(playerId uint, gameTag string) (float32, error) {
|
func (pc *PlayerCache) GetScore(playerId uint, gameTag string) (float32, error) {
|
||||||
key := GetPlayerCacheKey(playerId, gameTag)
|
key := getPlayerCacheKey(playerId, gameTag)
|
||||||
val, err := GetValue(key)
|
val, err := pc.GetValue(key)
|
||||||
if errors.Is(err, redis.Nil) {
|
if errors.Is(err, redis.Nil) {
|
||||||
return -1.0, nil // cache miss
|
return -1.0, nil // cache miss
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return -1.0, err // cache error
|
return -1.0, err // cache error
|
||||||
} else {
|
} 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) {
|
func (pc *PlayerCache) GetScores(playerIds []uint, gameTag string) ([]float32, error) {
|
||||||
vals, err := models.Cache.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
vals, err := pc.cache.Pipelined(ctx, func(pipe redis.Pipeliner) error {
|
||||||
for _, p := range players {
|
for _, id := range playerIds {
|
||||||
key := GetPlayerCacheKey(p.ID, gameTag)
|
key := getPlayerCacheKey(id, gameTag)
|
||||||
pipe.Get(ctx, key)
|
pipe.Get(ctx, key)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -69,15 +92,15 @@ func GetScores(players []models.Player, gameTag string) ([]float32, error) {
|
|||||||
return scores, nil
|
return scores, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func DeleteScore(playerId uint, gameTag string) error {
|
func (pc *PlayerCache) DeleteScore(playerId uint, gameTag string) error {
|
||||||
key := GetPlayerCacheKey(playerId, gameTag)
|
key := getPlayerCacheKey(playerId, gameTag)
|
||||||
return models.Cache.Del(ctx, key).Err()
|
return pc.cache.Del(ctx, key).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func PurgeCache() error {
|
func (pc *PlayerCache) PurgeCache() error {
|
||||||
return models.Cache.FlushAll(ctx).Err()
|
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)
|
return fmt.Sprintf("player:%d:game:%s", playerId, gameTag)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"cache"
|
||||||
"github.com/glebarez/sqlite"
|
"github.com/glebarez/sqlite"
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DB *gorm.DB
|
var DB *gorm.DB
|
||||||
var ctx = context.Background()
|
var PlayerCache *cache.PlayerCache
|
||||||
var Cache *redis.Client
|
|
||||||
|
|
||||||
func ConnectDatabase() {
|
func ConnectDatabase() {
|
||||||
database, err := gorm.Open(sqlite.Open("isc_data.db"), &gorm.Config{
|
database, err := gorm.Open(sqlite.Open("isc_data.db"), &gorm.Config{
|
||||||
@@ -61,25 +60,15 @@ func ConnectDatabase() {
|
|||||||
DB = database
|
DB = database
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConnectCache() {
|
func ConnectCache(playerCacheLifetime time.Duration) {
|
||||||
address := os.Getenv("REDIS_ADDRESS")
|
address := os.Getenv("REDIS_ADDRESS")
|
||||||
if address == "" {
|
if address == "" {
|
||||||
address = "127.0.0.1"
|
address = "127.0.0.1:6379"
|
||||||
}
|
|
||||||
port := os.Getenv("REDIS_PORT")
|
|
||||||
if port == "" {
|
|
||||||
port = "6379"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache = redis.NewClient(&redis.Options{
|
PlayerCache = cache.NewPlayerCache(address, playerCacheLifetime)
|
||||||
Addr: address + ":" + port,
|
if err := PlayerCache.Connect(); err != nil {
|
||||||
Password: "",
|
PlayerCache = nil
|
||||||
DB: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
_, err := Cache.Ping(ctx).Result()
|
|
||||||
if err != nil {
|
|
||||||
Cache = nil
|
|
||||||
log.Fatal("Failed to connect to Redis! " + err.Error())
|
log.Fatal("Failed to connect to Redis! " + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
{{ range . }}
|
{{ range . }}
|
||||||
<div class="input-group input-group-lg mb-1">
|
<div class="input-group input-group-lg mb-1">
|
||||||
<div class="input-group-text py-1 px-2">
|
<div class="input-group-text py-1 px-2">
|
||||||
<input class="form-check-input fs-4 border-secondary mt-0" type="checkbox" value="" onchange="updateSelectedPlayers(this)">
|
<label>
|
||||||
|
<input class="form-check-input fs-4 border-secondary mt-0" type="checkbox" value="" onchange="updateSelectedPlayers(this)">
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<span class="form-control py-2 px-3" style="width: 10em">{{ .PlayerName }}</span>
|
<span class="form-control py-2 px-3" style="width: 10em">{{ .PlayerName }}</span>
|
||||||
<div class="form-control text-center px-2 text-secondary">
|
<div class="form-control text-center px-2 text-secondary">
|
||||||
|
|||||||
Reference in New Issue
Block a user