Merge branch 'master' into test-deployment

This commit is contained in:
Jonas Pinson 2024-05-26 12:46:27 +02:00 committed by GitHub
commit a5277c9d39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 121 additions and 257 deletions

View File

@ -22,6 +22,7 @@ import (
"crypto/rand" "crypto/rand"
"database/sql" "database/sql"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
@ -43,7 +44,7 @@ func Login(username, password string) (LoginResponse, error) {
key, salt, err := db.FetchAccountKeySaltFromUsername(username) key, salt, err := db.FetchAccountKeySaltFromUsername(username)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if errors.Is(err, sql.ErrNoRows) {
return response, fmt.Errorf("account doesn't exist") return response, fmt.Errorf("account doesn't exist")
} }

View File

@ -21,18 +21,22 @@ import (
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log"
"net/http"
"github.com/pagefaultgames/rogueserver/api/account" "github.com/pagefaultgames/rogueserver/api/account"
"github.com/pagefaultgames/rogueserver/api/daily" "github.com/pagefaultgames/rogueserver/api/daily"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
"log"
"net/http"
) )
func Init(mux *http.ServeMux) error { func Init(mux *http.ServeMux) error {
if err := scheduleStatRefresh(); err != nil { err := scheduleStatRefresh()
if err != nil {
return err return err
} }
if err := daily.Init(); err != nil {
err = daily.Init()
if err != nil {
return err return err
} }
@ -56,7 +60,7 @@ func Init(mux *http.ServeMux) error {
// new session // new session
mux.HandleFunc("POST /savedata/updateall", handleUpdateAll) mux.HandleFunc("POST /savedata/updateall", handleUpdateAll)
mux.HandleFunc("POST /savedata/verify", handleSessionVerify) mux.HandleFunc("POST /savedata/system/verify", handleSystemVerify)
mux.HandleFunc("GET /savedata/system", handleGetSystemData) mux.HandleFunc("GET /savedata/system", handleGetSystemData)
mux.HandleFunc("GET /savedata/session", handleGetSessionData) mux.HandleFunc("GET /savedata/session", handleGetSessionData)
@ -64,6 +68,7 @@ func Init(mux *http.ServeMux) error {
mux.HandleFunc("GET /daily/seed", handleDailySeed) mux.HandleFunc("GET /daily/seed", handleDailySeed)
mux.HandleFunc("GET /daily/rankings", handleDailyRankings) mux.HandleFunc("GET /daily/rankings", handleDailyRankings)
mux.HandleFunc("GET /daily/rankingpagecount", handleDailyRankingPageCount) mux.HandleFunc("GET /daily/rankingpagecount", handleDailyRankingPageCount)
return nil return nil
} }
@ -86,7 +91,11 @@ func tokenFromRequest(r *http.Request) ([]byte, error) {
func uuidFromRequest(r *http.Request) ([]byte, error) { func uuidFromRequest(r *http.Request) ([]byte, error) {
_, uuid, err := tokenAndUuidFromRequest(r) _, uuid, err := tokenAndUuidFromRequest(r)
return uuid, err if err != nil {
return nil, err
}
return uuid, nil
} }
func tokenAndUuidFromRequest(r *http.Request) ([]byte, []byte, error) { func tokenAndUuidFromRequest(r *http.Request) ([]byte, []byte, error) {
@ -108,7 +117,7 @@ func httpError(w http.ResponseWriter, r *http.Request, err error, code int) {
http.Error(w, err.Error(), code) http.Error(w, err.Error(), code)
} }
func jsonResponse(w http.ResponseWriter, r *http.Request, data any) { func writeJSON(w http.ResponseWriter, r *http.Request, data any) {
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(data) err := json.NewEncoder(w).Encode(data)
if err != nil { if err != nil {

View File

@ -18,8 +18,6 @@
package daily package daily
import ( import (
"log"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
"github.com/pagefaultgames/rogueserver/defs" "github.com/pagefaultgames/rogueserver/defs"
) )
@ -28,7 +26,7 @@ import (
func Rankings(category, page int) ([]defs.DailyRanking, error) { func Rankings(category, page int) ([]defs.DailyRanking, error) {
rankings, err := db.FetchRankings(category, page) rankings, err := db.FetchRankings(category, page)
if err != nil { if err != nil {
log.Print("failed to retrieve rankings") return rankings, err
} }
return rankings, nil return rankings, nil

View File

@ -18,8 +18,6 @@
package daily package daily
import ( import (
"log"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
) )
@ -27,7 +25,7 @@ import (
func RankingPageCount(category int) (int, error) { func RankingPageCount(category int) (int, error) {
pageCount, err := db.FetchRankingPageCount(category) pageCount, err := db.FetchRankingPageCount(category)
if err != nil { if err != nil {
log.Print("failed to retrieve ranking page count") return pageCount, err
} }
return pageCount, nil return pageCount, nil

View File

@ -61,7 +61,7 @@ func handleAccountInfo(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, response) writeJSON(w, r, response)
} }
func handleAccountRegister(w http.ResponseWriter, r *http.Request) { func handleAccountRegister(w http.ResponseWriter, r *http.Request) {
@ -93,7 +93,7 @@ func handleAccountLogin(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, response) writeJSON(w, r, response)
} }
func handleAccountChangePW(w http.ResponseWriter, r *http.Request) { func handleAccountChangePW(w http.ResponseWriter, r *http.Request) {
@ -141,7 +141,7 @@ func handleGameTitleStats(w http.ResponseWriter, r *http.Request) {
BattleCount: battleCount, BattleCount: battleCount,
} }
jsonResponse(w, r, stats) writeJSON(w, r, stats)
} }
func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) { func handleGameClassicSessionCount(w http.ResponseWriter, r *http.Request) {
@ -189,7 +189,7 @@ func handleGetSessionData(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, save) writeJSON(w, r, save)
} }
const legacyClientSessionId = "LEGACY_CLIENT" const legacyClientSessionId = "LEGACY_CLIENT"
@ -239,11 +239,11 @@ func legacyHandleGetSaveData(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, save) writeJSON(w, r, save)
} }
// FIXME UNFINISHED!!! // FIXME UNFINISHED!!!
func clearSessionData(w http.ResponseWriter, r *http.Request) { /*func clearSessionData(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r) uuid, err := uuidFromRequest(r)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusBadRequest) httpError(w, r, err, http.StatusBadRequest)
@ -299,19 +299,19 @@ func clearSessionData(w http.ResponseWriter, r *http.Request) {
if storedTrainerId > 0 || storedSecretId > 0 { if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId { if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest)
return return
} }
} else { } else {
err = db.UpdateTrainerIds(trainerId, secretId, uuid) err = db.UpdateTrainerIds(trainerId, secretId, uuid)
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("unable to update traienr ID: %s", err), http.StatusInternalServerError) httpError(w, r, fmt.Errorf("unable to update trainer ID: %s", err), http.StatusInternalServerError)
return return
} }
} }
if !active { if !active {
save = savedata.ClearResponse{Error: "session out of date"} save = savedata.ClearResponse{Error: "session out of date: not active"}
} }
var seed string var seed string
@ -364,7 +364,7 @@ func deleteSystemSave(w http.ResponseWriter, r *http.Request) {
} }
if !active { if !active {
httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest)
return return
} }
@ -392,7 +392,7 @@ func deleteSystemSave(w http.ResponseWriter, r *http.Request) {
if storedTrainerId > 0 || storedSecretId > 0 { if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId { if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest)
return return
} }
} else { } else {
@ -409,7 +409,7 @@ func deleteSystemSave(w http.ResponseWriter, r *http.Request) {
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }*/
func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) { func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r) uuid, err := uuidFromRequest(r)
@ -487,7 +487,7 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
// TODO: make this not suck // TODO: make this not suck
if !active && r.URL.Path != "/savedata/clear" { if !active && r.URL.Path != "/savedata/clear" {
httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest)
return return
} }
@ -520,7 +520,7 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
if storedTrainerId > 0 || storedSecretId > 0 { if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId { if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest)
return return
} }
} else { } else {
@ -534,7 +534,7 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path { switch r.URL.Path {
case "/savedata/get": case "/savedata/get":
save, err = savedata.Get(uuid, datatype, slot) save, err = savedata.Get(uuid, datatype, slot)
if err == sql.ErrNoRows { if errors.Is(err, sql.ErrNoRows) {
http.Error(w, err.Error(), http.StatusNotFound) http.Error(w, err.Error(), http.StatusNotFound)
return return
} }
@ -545,7 +545,7 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
case "/savedata/clear": case "/savedata/clear":
if !active { if !active {
// TODO: make this not suck // TODO: make this not suck
save = savedata.ClearResponse{Error: "session out of date"} save = savedata.ClearResponse{Error: "session out of date: not active"}
break break
} }
@ -570,7 +570,7 @@ func legacyHandleSaveData(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, save) writeJSON(w, r, save)
} }
type CombinedSaveData struct { type CombinedSaveData struct {
@ -588,30 +588,25 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) {
return return
} }
var clientSessionId string
if r.URL.Query().Has("clientSessionId") {
clientSessionId = r.URL.Query().Get("clientSessionId")
}
if clientSessionId == "" {
clientSessionId = legacyClientSessionId
}
var data CombinedSaveData var data CombinedSaveData
err = json.NewDecoder(r.Body).Decode(&data) err = json.NewDecoder(r.Body).Decode(&data)
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
return return
} }
if data.ClientSessionId == "" {
data.ClientSessionId = legacyClientSessionId
}
var active bool var active bool
active, err = db.IsActiveSession(uuid, clientSessionId) active, err = db.IsActiveSession(uuid, data.ClientSessionId)
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest) httpError(w, r, fmt.Errorf("failed to check active session: %s", err), http.StatusBadRequest)
return return
} }
if !active { if !active {
httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date: not active"), http.StatusBadRequest)
return return
} }
@ -626,11 +621,11 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) {
if storedTrainerId > 0 || storedSecretId > 0 { if storedTrainerId > 0 || storedSecretId > 0 {
if trainerId != storedTrainerId || secretId != storedSecretId { if trainerId != storedTrainerId || secretId != storedSecretId {
httpError(w, r, fmt.Errorf("session out of date"), http.StatusBadRequest) httpError(w, r, fmt.Errorf("session out of date: stored trainer or secret ID does not match"), http.StatusBadRequest)
return return
} }
} else { } else {
if err := db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil { if err = db.UpdateTrainerIds(trainerId, secretId, uuid); err != nil {
httpError(w, r, err, http.StatusInternalServerError) httpError(w, r, err, http.StatusInternalServerError)
return return
} }
@ -649,24 +644,23 @@ func handleUpdateAll(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
type SessionVerifyResponse struct { type SystemVerifyResponse struct {
Valid bool `json:"valid"` Valid bool `json:"valid"`
SessionData *defs.SessionSaveData `json:"sessionData"` SystemData *defs.SystemSaveData `json:"systemData"`
} }
type SessionVerifyRequest struct { type SystemVerifyRequest struct {
ClientSessionId string `json:"clientSessionId"` ClientSessionId string `json:"clientSessionId"`
Slot int `json:"slot"`
} }
func handleSessionVerify(w http.ResponseWriter, r *http.Request) { func handleSystemVerify(w http.ResponseWriter, r *http.Request) {
uuid, err := uuidFromRequest(r) uuid, err := uuidFromRequest(r)
if err != nil { if err != nil {
httpError(w, r, err, http.StatusBadRequest) httpError(w, r, err, http.StatusBadRequest)
return return
} }
var input SessionVerifyRequest var input SystemVerifyRequest
err = json.NewDecoder(r.Body).Decode(&input) err = json.NewDecoder(r.Body).Decode(&input)
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest) httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
@ -680,7 +674,7 @@ func handleSessionVerify(w http.ResponseWriter, r *http.Request) {
return return
} }
response := SessionVerifyResponse{ response := SystemVerifyResponse{
Valid: active, Valid: active,
} }
@ -692,17 +686,23 @@ func handleSessionVerify(w http.ResponseWriter, r *http.Request) {
return return
} }
var storedSaveData defs.SessionSaveData var storedSaveData defs.SystemSaveData
storedSaveData, err = db.ReadSessionSaveData(uuid, input.Slot) storedSaveData, err = db.ReadSystemSaveData(uuid)
if err != nil { if err != nil {
httpError(w, r, fmt.Errorf("failed to read session save data: %s", err), http.StatusInternalServerError) httpError(w, r, fmt.Errorf("failed to read session save data: %s", err), http.StatusInternalServerError)
return return
} }
response.SessionData = &storedSaveData response.SystemData = &storedSaveData
} }
jsonResponse(w, r, response) err = db.UpdateAccountLastActivity(uuid)
if err != nil {
httpError(w, r, fmt.Errorf("failed to update account last activity: %s", err), http.StatusInternalServerError)
return
}
writeJSON(w, r, response)
} }
func handleGetSystemData(w http.ResponseWriter, r *http.Request) { func handleGetSystemData(w http.ResponseWriter, r *http.Request) {
@ -736,8 +736,9 @@ func handleGetSystemData(w http.ResponseWriter, r *http.Request) {
return return
} }
//TODO apply vouchers
jsonResponse(w, r, save) writeJSON(w, r, save)
} }
func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) { func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) {
@ -762,7 +763,7 @@ func legacyHandleNewClear(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, newClear) writeJSON(w, r, newClear)
} }
// daily // daily
@ -806,7 +807,7 @@ func handleDailyRankings(w http.ResponseWriter, r *http.Request) {
return return
} }
jsonResponse(w, r, rankings) writeJSON(w, r, rankings)
} }
func handleDailyRankingPageCount(w http.ResponseWriter, r *http.Request) { func handleDailyRankingPageCount(w http.ResponseWriter, r *http.Request) {

View File

@ -50,6 +50,10 @@ func Clear(uuid []byte, slot int, seed string, save defs.SessionSaveData) (Clear
waveCompleted-- waveCompleted--
} }
if save.Score >= 20000 {
db.SetAccountBanned(uuid, true)
}
err = db.AddOrUpdateAccountDailyRun(uuid, save.Score, waveCompleted) err = db.AddOrUpdateAccountDailyRun(uuid, save.Score, waveCompleted)
if err != nil { if err != nil {
log.Printf("failed to add or update daily run record: %s", err) log.Printf("failed to add or update daily run record: %s", err)

View File

@ -19,9 +19,10 @@ package savedata
import ( import (
"fmt" "fmt"
"log"
"github.com/pagefaultgames/rogueserver/db" "github.com/pagefaultgames/rogueserver/db"
"github.com/pagefaultgames/rogueserver/defs" "github.com/pagefaultgames/rogueserver/defs"
"log"
) )
// /savedata/delete - delete save data // /savedata/delete - delete save data
@ -33,14 +34,20 @@ func Delete(uuid []byte, datatype, slot int) error {
switch datatype { switch datatype {
case 0: // System case 0: // System
return db.DeleteSystemSaveData(uuid) err = db.DeleteSystemSaveData(uuid)
case 1: // Session case 1: // Session
if slot < 0 || slot >= defs.SessionSlotCount { if slot < 0 || slot >= defs.SessionSlotCount {
return fmt.Errorf("slot id %d out of range", slot) err = fmt.Errorf("slot id %d out of range", slot)
break
} }
return db.DeleteSessionSaveData(uuid, slot) err = db.DeleteSessionSaveData(uuid, slot)
default: default:
return fmt.Errorf("invalid data type") err = fmt.Errorf("invalid data type")
} }
if err != nil {
return err
}
return nil
} }

View File

@ -44,7 +44,7 @@ func Get(uuid []byte, datatype, slot int) (any, error) {
return nil, fmt.Errorf("failed to fetch compensations: %s", err) return nil, fmt.Errorf("failed to fetch compensations: %s", err)
} }
needsUpdate := false var needsUpdate bool
for compensationType, amount := range compensations { for compensationType, amount := range compensations {
system.VoucherCounts[strconv.Itoa(compensationType)] += amount system.VoucherCounts[strconv.Itoa(compensationType)] += amount
if amount > 0 { if amount > 0 {
@ -57,6 +57,10 @@ func Get(uuid []byte, datatype, slot int) (any, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to update system save data: %s", err) return nil, fmt.Errorf("failed to update system save data: %s", err)
} }
err = db.DeleteClaimedAccountCompensations(uuid)
if err != nil {
return nil, fmt.Errorf("failed to delete claimed compensations: %s", err)
}
err = db.UpdateAccountStats(uuid, system.GameStats, system.VoucherCounts) err = db.UpdateAccountStats(uuid, system.GameStats, system.VoucherCounts)
if err != nil { if err != nil {

View File

@ -42,6 +42,13 @@ func Update(uuid []byte, slot int, save any) error {
return fmt.Errorf("client version out of date") return fmt.Errorf("client version out of date")
} }
if save.VoucherCounts["0"] > 300 ||
save.VoucherCounts["1"] > 150 ||
save.VoucherCounts["2"] > 100 ||
save.VoucherCounts["3"] > 10 {
db.SetAccountBanned(uuid, true)
}
err = db.UpdateAccountStats(uuid, save.GameStats, save.VoucherCounts) err = db.UpdateAccountStats(uuid, save.GameStats, save.VoucherCounts)
if err != nil { if err != nil {
return fmt.Errorf("failed to update account stats: %s", err) return fmt.Errorf("failed to update account stats: %s", err)

View File

@ -145,6 +145,15 @@ func UpdateAccountStats(uuid []byte, stats defs.GameStats, voucherCounts map[str
return nil return nil
} }
func SetAccountBanned(uuid []byte, banned bool) error {
_, err := handle.Exec("UPDATE accounts SET banned = ? WHERE uuid = ?", banned, uuid)
if err != nil {
return err
}
return nil
}
func FetchAndClaimAccountCompensations(uuid []byte) (map[int]int, error) { func FetchAndClaimAccountCompensations(uuid []byte) (map[int]int, error) {
var compensations = make(map[int]int) var compensations = make(map[int]int)
@ -210,28 +219,27 @@ func UpdateTrainerIds(trainerId, secretId int, uuid []byte) error {
return nil return nil
} }
func IsActiveSession(uuid []byte, clientSessionId string) (bool, error) { func IsActiveSession(uuid []byte, sessionId string) (bool, error) {
var storedId string var id string
err := handle.QueryRow("SELECT clientSessionId FROM activeClientSessions WHERE sessions.uuid = ?", uuid).Scan(&storedId) err := handle.QueryRow("SELECT clientSessionId FROM activeClientSessions WHERE uuid = ?", uuid).Scan(&id)
if err != nil { if err != nil {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
return false, nil err = UpdateActiveSession(uuid, sessionId)
if err != nil {
return false, err
}
return true, nil
} }
return false, err return false, err
} }
if storedId == "" {
err = UpdateActiveSession(uuid, clientSessionId)
if err != nil {
return false, err
}
return true, nil
}
return storedId == clientSessionId, nil return id == "" || id == sessionId, nil
} }
func UpdateActiveSession(uuid []byte, clientSessionId string) error { func UpdateActiveSession(uuid []byte, clientSessionId string) error {
_, err := handle.Exec("REPLACE INTO activeClientSessions VALUES (?, ?)", uuid, clientSessionId) _, err := handle.Exec("INSERT INTO activeClientSessions (uuid, clientSessionId) VALUES (?, ?) ON DUPLICATE KEY UPDATE clientSessionId = ?", uuid, clientSessionId, clientSessionId)
if err != nil { if err != nil {
return err return err
} }

View File

@ -41,7 +41,6 @@ func GetDailyRunSeed() (string, error) {
} }
return seed, nil return seed, nil
} }
func AddOrUpdateAccountDailyRun(uuid []byte, score int, wave int) error { func AddOrUpdateAccountDailyRun(uuid []byte, score int, wave int) error {

View File

@ -19,11 +19,8 @@ package db
import ( import (
"database/sql" "database/sql"
"encoding/hex"
"fmt" "fmt"
"log" "log"
"os"
"time"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
) )
@ -38,15 +35,10 @@ func Init(username, password, protocol, address, database string) error {
return fmt.Errorf("failed to open database connection: %s", err) return fmt.Errorf("failed to open database connection: %s", err)
} }
conns := 1024 conns := 64
if protocol != "unix" {
conns = 256
}
handle.SetMaxOpenConns(conns) handle.SetMaxOpenConns(conns)
handle.SetMaxIdleConns(conns / 4) handle.SetMaxIdleConns(conns)
handle.SetConnMaxIdleTime(time.Second * 10)
tx, err := handle.Begin() tx, err := handle.Begin()
if err != nil { if err != nil {
@ -56,7 +48,7 @@ func Init(username, password, protocol, address, database string) error {
err = setupDb(tx) err = setupDb(tx)
if err != nil { if err != nil {
_ = tx.Rollback() tx.Rollback()
log.Fatal(err) log.Fatal(err)
} }
@ -65,83 +57,6 @@ func Init(username, password, protocol, address, database string) error {
log.Fatal(err) log.Fatal(err)
} }
// TODO temp code
_, err = os.Stat("userdata")
if err != nil {
if !os.IsNotExist(err) { // not found, do not migrate
log.Fatalf("failed to stat userdata directory: %s", err)
}
return nil
}
entries, err := os.ReadDir("userdata")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
uuidString := entry.Name()
uuid, err := hex.DecodeString(uuidString)
if err != nil {
log.Printf("failed to decode uuid: %s", err)
continue
}
var count int
err = handle.QueryRow("SELECT COUNT(*) FROM systemSaveData WHERE uuid = ?", uuid).Scan(&count)
if err != nil || count != 0 {
continue
}
// store new system data
systemData, err := LegacyReadSystemSaveData(uuid)
if err != nil {
log.Printf("failed to read system save data for %v: %s", uuidString, err)
continue
}
err = StoreSystemSaveData(uuid, systemData)
if err != nil {
log.Fatalf("failed to store system save data for %v: %s\n", uuidString, err)
}
// delete old system data
err = os.Remove("userdata/" + uuidString + "/system.pzs")
if err != nil {
log.Fatalf("failed to remove legacy system save data for %v: %s", uuidString, err)
}
for i := 0; i < 5; i++ {
sessionData, err := LegacyReadSessionSaveData(uuid, i)
if err != nil {
log.Printf("failed to read session save data %v for %v: %s", i, uuidString, err)
continue
}
// store new session data
err = StoreSessionSaveData(uuid, sessionData, i)
if err != nil {
log.Fatalf("failed to store session save data for %v: %s\n", uuidString, err)
}
// delete old session data
filename := "session"
if i != 0 {
filename += fmt.Sprintf("%d", i)
}
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", uuidString, filename))
if err != nil {
log.Fatalf("failed to remove legacy session save data %v for %v: %s", i, uuidString, err)
}
}
}
return nil return nil
} }
@ -169,9 +84,9 @@ func setupDb(tx *sql.Tx) error {
`CREATE TABLE IF NOT EXISTS accountDailyRuns (uuid BINARY(16) NOT NULL, date DATE NOT NULL, score INT(11) NOT NULL DEFAULT 0, wave INT(11) NOT NULL DEFAULT 0, timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (uuid, date), CONSTRAINT accountDailyRuns_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT accountDailyRuns_ibfk_2 FOREIGN KEY (date) REFERENCES dailyRuns (date) ON DELETE NO ACTION ON UPDATE NO ACTION)`, `CREATE TABLE IF NOT EXISTS accountDailyRuns (uuid BINARY(16) NOT NULL, date DATE NOT NULL, score INT(11) NOT NULL DEFAULT 0, wave INT(11) NOT NULL DEFAULT 0, timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (uuid, date), CONSTRAINT accountDailyRuns_ibfk_1 FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT accountDailyRuns_ibfk_2 FOREIGN KEY (date) REFERENCES dailyRuns (date) ON DELETE NO ACTION ON UPDATE NO ACTION)`,
`CREATE INDEX IF NOT EXISTS accountDailyRunsByDate ON accountDailyRuns (date)`, `CREATE INDEX IF NOT EXISTS accountDailyRunsByDate ON accountDailyRuns (date)`,
`CREATE TABLE IF NOT EXISTS systemSaveData (uuid BINARY(16) PRIMARY KEY, data LONGBLOB, timestamp TIMESTAMP)`, `CREATE TABLE IF NOT EXISTS systemSaveData (uuid BINARY(16) PRIMARY KEY, data LONGBLOB, timestamp TIMESTAMP, FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`,
`CREATE TABLE IF NOT EXISTS sessionSaveData (uuid BINARY(16), slot TINYINT, data LONGBLOB, timestamp TIMESTAMP, PRIMARY KEY (uuid, slot))`, `CREATE TABLE IF NOT EXISTS sessionSaveData (uuid BINARY(16), slot TINYINT, data LONGBLOB, timestamp TIMESTAMP, PRIMARY KEY (uuid, slot), FOREIGN KEY (uuid) REFERENCES accounts (uuid) ON DELETE CASCADE ON UPDATE CASCADE)`,
// ---------------------------------- // ----------------------------------
// MIGRATION 001 // MIGRATION 001

View File

@ -29,7 +29,7 @@ func FetchPlayerCount() (int, error) {
func FetchBattleCount() (int, error) { func FetchBattleCount() (int, error) {
var battleCount int var battleCount int
err := handle.QueryRow("SELECT COALESCE(SUM(battles), 0) FROM accountStats").Scan(&battleCount) err := handle.QueryRow("SELECT COALESCE(SUM(s.battles), 0) FROM accountStats s JOIN accounts a ON a.uuid = s.uuid WHERE a.banned = 0").Scan(&battleCount)
if err != nil { if err != nil {
return 0, err return 0, err
} }
@ -39,7 +39,7 @@ func FetchBattleCount() (int, error) {
func FetchClassicSessionCount() (int, error) { func FetchClassicSessionCount() (int, error) {
var classicSessionCount int var classicSessionCount int
err := handle.QueryRow("SELECT COALESCE(SUM(classicSessionsPlayed), 0) FROM accountStats").Scan(&classicSessionCount) err := handle.QueryRow("SELECT COALESCE(SUM(s.classicSessionsPlayed), 0) FROM accountStats s JOIN accounts a ON a.uuid = s.uuid WHERE a.banned = 0").Scan(&classicSessionCount)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -1,84 +0,0 @@
/*
Copyright (C) 2024 Pagefault Games
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package db
import (
"encoding/gob"
"encoding/hex"
"fmt"
"os"
"strconv"
"github.com/klauspost/compress/zstd"
"github.com/pagefaultgames/rogueserver/defs"
)
func LegacyReadSystemSaveData(uuid []byte) (defs.SystemSaveData, error) {
var system defs.SystemSaveData
file, err := os.Open("userdata/" + hex.EncodeToString(uuid) + "/system.pzs")
if err != nil {
return system, fmt.Errorf("failed to open save file for reading: %s", err)
}
defer file.Close()
zstdDecoder, err := zstd.NewReader(file)
if err != nil {
return system, fmt.Errorf("failed to create zstd decoder: %s", err)
}
defer zstdDecoder.Close()
err = gob.NewDecoder(zstdDecoder).Decode(&system)
if err != nil {
return system, fmt.Errorf("failed to deserialize save: %s", err)
}
return system, nil
}
func LegacyReadSessionSaveData(uuid []byte, slotID int) (defs.SessionSaveData, error) {
var session defs.SessionSaveData
fileName := "session"
if slotID != 0 {
fileName += strconv.Itoa(slotID)
}
file, err := os.Open(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
if err != nil {
return session, fmt.Errorf("failed to open save file for reading: %s", err)
}
defer file.Close()
zstdDecoder, err := zstd.NewReader(file)
if err != nil {
return session, fmt.Errorf("failed to create zstd decoder: %s", err)
}
defer zstdDecoder.Close()
err = gob.NewDecoder(zstdDecoder).Decode(&session)
if err != nil {
return session, fmt.Errorf("failed to deserialize save: %s", err)
}
return session, nil
}

1
go.mod
View File

@ -4,7 +4,6 @@ go 1.22
require ( require (
github.com/go-sql-driver/mysql v1.7.1 github.com/go-sql-driver/mysql v1.7.1
github.com/klauspost/compress v1.17.4
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
golang.org/x/crypto v0.16.0 golang.org/x/crypto v0.16.0
) )

2
go.sum
View File

@ -1,7 +1,5 @@
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=