Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bb1d198a5 |
No files matched your search
@@ -30,7 +30,7 @@ func getUsernameFromRequest(r *http.Request) (string, error) {
|
|||||||
return username, nil
|
return username, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUUIDFromRequest(r *http.Request) ([]byte, error) {
|
func getUuidFromRequest(r *http.Request) ([]byte, error) {
|
||||||
if r.Header.Get("Authorization") == "" {
|
if r.Header.Get("Authorization") == "" {
|
||||||
return nil, fmt.Errorf("missing token")
|
return nil, fmt.Errorf("missing token")
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ func getUUIDFromRequest(r *http.Request) ([]byte, error) {
|
|||||||
return nil, fmt.Errorf("invalid token length: got %d, expected 32", len(token))
|
return nil, fmt.Errorf("invalid token length: got %d, expected 32", len(token))
|
||||||
}
|
}
|
||||||
|
|
||||||
uuid, err := db.FetchUUIDFromToken(token)
|
uuid, err := db.FetchUuidFromToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to validate token: %s", err)
|
return nil, fmt.Errorf("failed to validate token: %s", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+110
-51
@@ -5,7 +5,9 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -16,12 +18,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UUIDSize = 16
|
argonTime = 1
|
||||||
ArgonTime = 1
|
argonMemory = 256 * 1024
|
||||||
ArgonMemory = 256 * 1024
|
argonThreads = 4
|
||||||
ArgonThreads = 4
|
argonKeyLength = 32
|
||||||
ArgonKeySize = 32
|
|
||||||
ArgonSaltSize = 16
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var isValidUsername = regexp.MustCompile(`^\w{1,16}$`).MatchString
|
var isValidUsername = regexp.MustCompile(`^\w{1,16}$`).MatchString
|
||||||
@@ -32,9 +32,21 @@ type AccountInfoResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// /account/info - get account info
|
// /account/info - get account info
|
||||||
func handleAccountInfo(username string, uuid []byte) (AccountInfoResponse, error) {
|
func (s *Server) handleAccountInfo(w http.ResponseWriter, r *http.Request) {
|
||||||
var latestSave time.Time
|
username, err := getUsernameFromRequest(r)
|
||||||
latestSaveID := -1
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid, err := getUuidFromRequest(r) // lazy
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestSaveTime time.Time
|
||||||
|
latestSaveId := -1
|
||||||
for id := range sessionSlotCount {
|
for id := range sessionSlotCount {
|
||||||
fileName := "session"
|
fileName := "session"
|
||||||
if id != 0 {
|
if id != 0 {
|
||||||
@@ -46,101 +58,148 @@ func handleAccountInfo(username string, uuid []byte) (AccountInfoResponse, error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if stat.ModTime().After(latestSave) {
|
if stat.ModTime().After(latestSaveTime) {
|
||||||
latestSave = stat.ModTime()
|
latestSaveTime = stat.ModTime()
|
||||||
latestSaveID = id
|
latestSaveId = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return AccountInfoResponse{Username: username, LastSessionSlot: latestSaveID}, nil
|
response, err := json.Marshal(AccountInfoResponse{Username: username, LastSessionSlot: latestSaveId})
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountRegisterRequest GenericAuthRequest
|
type AccountRegisterRequest GenericAuthRequest
|
||||||
|
|
||||||
// /account/register - register account
|
// /account/register - register account
|
||||||
func handleAccountRegister(username, password string) error {
|
func (s *Server) handleAccountRegister(w http.ResponseWriter, r *http.Request) {
|
||||||
if !isValidUsername(username) {
|
var request AccountRegisterRequest
|
||||||
return fmt.Errorf("invalid username")
|
err := json.NewDecoder(r.Body).Decode(&request)
|
||||||
}
|
|
||||||
|
|
||||||
if len(password) < 6 {
|
|
||||||
return fmt.Errorf("invalid password")
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid := make([]byte, UUIDSize)
|
|
||||||
_, err := rand.Read(uuid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate uuid: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to decode request body: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
salt := make([]byte, ArgonSaltSize)
|
if !isValidUsername(request.Username) {
|
||||||
|
httpError(w, r, "invalid username", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(request.Password) < 6 {
|
||||||
|
httpError(w, r, "invalid password", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := make([]byte, 16)
|
||||||
|
_, err = rand.Read(uuid)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to generate uuid: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, 16)
|
||||||
_, err = rand.Read(salt)
|
_, err = rand.Read(salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf(fmt.Sprintf("failed to generate salt: %s", err))
|
httpError(w, r, fmt.Sprintf("failed to generate salt: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AddAccountRecord(uuid, username, argon2.IDKey([]byte(password), salt, ArgonTime, ArgonMemory, ArgonThreads, ArgonKeySize), salt)
|
err = db.AddAccountRecord(uuid, request.Username, argon2.IDKey([]byte(request.Password), salt, argonTime, argonMemory, argonThreads, argonKeyLength), salt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to add account record: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to add account record: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountLoginRequest GenericAuthRequest
|
type AccountLoginRequest GenericAuthRequest
|
||||||
type AccountLoginResponse GenericAuthResponse
|
type AccountLoginResponse GenericAuthResponse
|
||||||
|
|
||||||
// /account/login - log into account
|
// /account/login - log into account
|
||||||
func handleAccountLogin(username, password string) (AccountLoginResponse, error) {
|
func (s *Server) handleAccountLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
if !isValidUsername(username) {
|
var request AccountLoginRequest
|
||||||
return AccountLoginResponse{}, fmt.Errorf("invalid username")
|
err := json.NewDecoder(r.Body).Decode(&request)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to decode request body: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(password) < 6 {
|
if !isValidUsername(request.Username) {
|
||||||
return AccountLoginResponse{}, fmt.Errorf("invalid password")
|
httpError(w, r, "invalid username", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key, salt, err := db.FetchAccountKeySaltFromUsername(username)
|
if len(request.Password) < 6 {
|
||||||
|
httpError(w, r, "invalid password", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, salt, err := db.FetchAccountKeySaltFromUsername(request.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return AccountLoginResponse{}, fmt.Errorf("account doesn't exist")
|
httpError(w, r, "account doesn't exist", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return AccountLoginResponse{}, err
|
httpError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !bytes.Equal(key, argon2.IDKey([]byte(password), salt, ArgonTime, ArgonMemory, ArgonThreads, ArgonKeySize)) {
|
if !bytes.Equal(key, argon2.IDKey([]byte(request.Password), salt, argonTime, argonMemory, argonThreads, argonKeyLength)) {
|
||||||
return AccountLoginResponse{}, fmt.Errorf("password doesn't match")
|
httpError(w, r, "password doesn't match", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token := make([]byte, 32)
|
token := make([]byte, 32)
|
||||||
_, err = rand.Read(token)
|
_, err = rand.Read(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AccountLoginResponse{}, fmt.Errorf("failed to generate token: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to generate token: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.AddAccountSession(username, token)
|
err = db.AddAccountSession(request.Username, token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return AccountLoginResponse{}, fmt.Errorf("failed to add account session")
|
httpError(w, r, "failed to add account session", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return AccountLoginResponse{Token: base64.StdEncoding.EncodeToString(token)}, nil
|
response, err := json.Marshal(AccountLoginResponse{Token: base64.StdEncoding.EncodeToString(token)})
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /account/logout - log out of account
|
// /account/logout - log out of account
|
||||||
func handleAccountLogout(token []byte) error {
|
func (s *Server) handleAccountLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
if len(token) != 32 {
|
token, err := base64.StdEncoding.DecodeString(r.Header.Get("Authorization"))
|
||||||
return fmt.Errorf("invalid token")
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to decode token: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := db.RemoveSessionFromToken(token)
|
if len(token) != 32 {
|
||||||
|
httpError(w, r, "invalid token", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.RemoveSessionFromToken(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return fmt.Errorf("token not found")
|
httpError(w, r, "token not found", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("failed to remove account session")
|
httpError(w, r, "failed to remove account session", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
+65
-20
@@ -5,13 +5,15 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Flashfyre/pokerogue-server/db"
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
"github.com/Flashfyre/pokerogue-server/defs"
|
|
||||||
"github.com/go-co-op/gocron"
|
"github.com/go-co-op/gocron"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,33 +26,26 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func ScheduleDailyRunRefresh() {
|
func ScheduleDailyRunRefresh() {
|
||||||
dailyRunScheduler.Every(1).Day().At("00:00").Do(func() error {
|
dailyRunScheduler.Every(1).Day().At("00:00").Do(InitDailyRun)
|
||||||
err := InitDailyRun()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}())
|
|
||||||
dailyRunScheduler.StartAsync()
|
dailyRunScheduler.StartAsync()
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitDailyRun() error {
|
func InitDailyRun() {
|
||||||
secret, err := os.ReadFile("secret.key")
|
secret, err := os.ReadFile("secret.key")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("failed to read daily seed secret: %s", err)
|
log.Fatalf("failed to read daily seed secret: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
newSecret := make([]byte, 32)
|
newSecret := make([]byte, 32)
|
||||||
_, err := rand.Read(newSecret)
|
_, err := rand.Read(newSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate daily seed secret: %s", err)
|
log.Fatalf("failed to generate daily seed secret: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile("secret.key", newSecret, 0400)
|
err = os.WriteFile("secret.key", newSecret, 0400)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write daily seed secret: %s", err)
|
log.Fatalf("failed to write daily seed secret: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
secret = newSecret
|
secret = newSecret
|
||||||
@@ -66,8 +61,6 @@ func InitDailyRun() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Daily Run Seed: %s", dailyRunSeed)
|
log.Printf("Daily Run Seed: %s", dailyRunSeed)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deriveDailyRunSeed(seedTime time.Time) []byte {
|
func deriveDailyRunSeed(seedTime time.Time) []byte {
|
||||||
@@ -79,27 +72,79 @@ func deriveDailyRunSeed(seedTime time.Time) []byte {
|
|||||||
return hashedSeed[:]
|
return hashedSeed[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /daily/seed - fetch daily run seed
|
||||||
|
func (s *Server) handleSeed(w http.ResponseWriter) {
|
||||||
|
w.Write([]byte(dailyRunSeed))
|
||||||
|
}
|
||||||
|
|
||||||
// /daily/rankings - fetch daily rankings
|
// /daily/rankings - fetch daily rankings
|
||||||
func handleRankings(uuid []byte, category, page int) ([]defs.DailyRanking, error) {
|
func (s *Server) handleRankings(w http.ResponseWriter, r *http.Request) {
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
uuid, err := getUuidFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.UpdateAccountLastActivity(uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("failed to update account last activity")
|
log.Print("failed to update account last activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var category int
|
||||||
|
if r.URL.Query().Has("category") {
|
||||||
|
category, err = strconv.Atoi(r.URL.Query().Get("category"))
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to convert category: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
page := 1
|
||||||
|
if r.URL.Query().Has("page") {
|
||||||
|
page, err = strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to convert page: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rankings, err := db.FetchRankings(category, page)
|
rankings, err := db.FetchRankings(category, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("failed to retrieve rankings")
|
log.Print("failed to retrieve rankings")
|
||||||
}
|
}
|
||||||
|
|
||||||
return rankings, nil
|
response, err := json.Marshal(rankings)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /daily/rankingpagecount - fetch daily ranking page count
|
// /daily/rankingpagecount - fetch daily ranking page count
|
||||||
func handleRankingPageCount(category int) (int, error) {
|
func (s *Server) handleRankingPageCount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var err error
|
||||||
|
var category int
|
||||||
|
|
||||||
|
if r.URL.Query().Has("category") {
|
||||||
|
category, err = strconv.Atoi(r.URL.Query().Get("category"))
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to convert category: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pageCount, err := db.FetchRankingPageCount(category)
|
pageCount, err := db.FetchRankingPageCount(category)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("failed to retrieve ranking page count")
|
log.Print("failed to retrieve ranking page count")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageCount, nil
|
response, err := json.Marshal(pageCount)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
}
|
}
|
||||||
+3
-3
@@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func httpError(w http.ResponseWriter, r *http.Request, err error, code int) {
|
func httpError(w http.ResponseWriter, r *http.Request, error string, code int) {
|
||||||
log.Printf("%s: %s\n", r.URL.Path, err)
|
log.Printf("%s: %s\n", r.URL.Path, error)
|
||||||
http.Error(w, err.Error(), code)
|
http.Error(w, error, code)
|
||||||
}
|
}
|
||||||
+41
-2
@@ -1,10 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Flashfyre/pokerogue-server/db"
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
|
"github.com/Flashfyre/pokerogue-server/defs"
|
||||||
"github.com/go-co-op/gocron"
|
"github.com/go-co-op/gocron"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,14 +30,49 @@ func updateStats() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
battleCount, err = db.FetchBattleCount()
|
battleCount, err = db.FetchBattleCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
classicSessionCount, err = db.FetchClassicSessionCount()
|
classicSessionCount, err = db.FetchClassicSessionCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /game/playercount - get player count
|
||||||
|
func (s *Server) handlePlayerCountGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response, err := json.Marshal(playerCount)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /game/titlestats - get title stats
|
||||||
|
func (s *Server) handleTitleStatsGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
titleStats := &defs.TitleStats{
|
||||||
|
PlayerCount: playerCount,
|
||||||
|
BattleCount: battleCount,
|
||||||
|
}
|
||||||
|
response, err := json.Marshal(titleStats)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /game/classicsessioncount - get classic session count
|
||||||
|
func (s *Server) handleClassicSessionCountGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
response, err := json.Marshal(classicSessionCount)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(response)
|
||||||
|
}
|
||||||
+14
-228
@@ -1,26 +1,14 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Flashfyre/pokerogue-server/defs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
The caller of endpoint handler functions are responsible for extracting the necessary data from the request.
|
|
||||||
Handler functions are responsible for checking the validity of this data and returning a result or error.
|
|
||||||
Handlers should not return serialized JSON, instead return the struct itself.
|
|
||||||
*/
|
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
gob.Register([]interface{}{})
|
gob.Register([]interface{}{})
|
||||||
gob.Register(map[string]interface{}{})
|
gob.Register(map[string]interface{}{})
|
||||||
@@ -37,239 +25,37 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
// /account
|
|
||||||
case "/account/info":
|
case "/account/info":
|
||||||
username, err := getUsernameFromRequest(r)
|
s.handleAccountInfo(w, r)
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid, err := getUUIDFromRequest(r) // lazy
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := handleAccountInfo(username, uuid)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := json.Marshal(info)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(response)
|
|
||||||
case "/account/register":
|
case "/account/register":
|
||||||
var request AccountRegisterRequest
|
s.handleAccountRegister(w, r)
|
||||||
err := json.NewDecoder(r.Body).Decode(&request)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = handleAccountRegister(request.Username, request.Password)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
case "/account/login":
|
case "/account/login":
|
||||||
var request AccountLoginRequest
|
s.handleAccountLogin(w, r)
|
||||||
err := json.NewDecoder(r.Body).Decode(&request)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
token, err := handleAccountLogin(request.Username, request.Password)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := json.Marshal(token)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(response)
|
|
||||||
case "/account/logout":
|
case "/account/logout":
|
||||||
token, err := base64.StdEncoding.DecodeString(r.Header.Get("Authorization"))
|
s.handleAccountLogout(w, r)
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to decode token: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = handleAccountLogout(token)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
|
|
||||||
// /game
|
|
||||||
case "/game/playercount":
|
case "/game/playercount":
|
||||||
w.Write([]byte(strconv.Itoa(playerCount)))
|
s.handlePlayerCountGet(w, r)
|
||||||
case "/game/titlestats":
|
case "/game/titlestats":
|
||||||
response, err := json.Marshal(&defs.TitleStats{
|
s.handleTitleStatsGet(w, r)
|
||||||
PlayerCount: playerCount,
|
|
||||||
BattleCount: battleCount,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(response)
|
|
||||||
case "/game/classicsessioncount":
|
case "/game/classicsessioncount":
|
||||||
w.Write([]byte(strconv.Itoa(classicSessionCount)))
|
s.handleClassicSessionCountGet(w, r)
|
||||||
|
|
||||||
// /savedata
|
|
||||||
case "/savedata/get", "/savedata/update", "/savedata/delete", "/savedata/clear":
|
|
||||||
uuid, err := getUUIDFromRequest(r)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
datatype := -1
|
|
||||||
if r.URL.Query().Has("datatype") {
|
|
||||||
datatype, err = strconv.Atoi(r.URL.Query().Get("datatype"))
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var slot int
|
|
||||||
if r.URL.Query().Has("slot") {
|
|
||||||
slot, err = strconv.Atoi(r.URL.Query().Get("slot"))
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var save any
|
|
||||||
// /savedata/get and /savedata/delete specify datatype, but don't expect data in body
|
|
||||||
if r.URL.Path != "/savedata/get" && r.URL.Path != "/savedata/delete" {
|
|
||||||
if datatype == 0 {
|
|
||||||
var system defs.SystemSaveData
|
|
||||||
err = json.NewDecoder(r.Body).Decode(&system)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
save = system
|
|
||||||
// /savedata/clear doesn't specify datatype, it is assumed to be 1 (session)
|
|
||||||
} else if datatype == 1 || r.URL.Path == "/savedata/clear" {
|
|
||||||
var session defs.SessionSaveData
|
|
||||||
err = json.NewDecoder(r.Body).Decode(&session)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to decode request body: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
save = session
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch r.URL.Path {
|
|
||||||
case "/savedata/get":
|
case "/savedata/get":
|
||||||
save, err = handleSavedataGet(uuid, datatype, slot)
|
s.handleSavedataGet(w, r)
|
||||||
case "/savedata/update":
|
case "/savedata/update":
|
||||||
err = handleSavedataUpdate(uuid, slot, save)
|
s.handleSavedataUpdate(w, r)
|
||||||
case "/savedata/delete":
|
case "/savedata/delete":
|
||||||
err = handleSavedataDelete(uuid, datatype, slot)
|
s.handleSavedataDelete(w, r)
|
||||||
case "/savedata/clear":
|
case "/savedata/clear":
|
||||||
// doesn't return a save, but it works
|
s.handleSavedataClear(w, r)
|
||||||
save, err = handleSavedataClear(uuid, slot, save.(defs.SessionSaveData))
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if save == nil || r.URL.Path == "/savedata/update" {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := json.Marshal(save)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(response)
|
|
||||||
|
|
||||||
// /daily
|
|
||||||
case "/daily/seed":
|
case "/daily/seed":
|
||||||
w.Write([]byte(dailyRunSeed))
|
s.handleSeed(w)
|
||||||
case "/daily/rankings":
|
case "/daily/rankings":
|
||||||
uuid, err := getUUIDFromRequest(r)
|
s.handleRankings(w, r)
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var category int
|
|
||||||
if r.URL.Query().Has("category") {
|
|
||||||
category, err = strconv.Atoi(r.URL.Query().Get("category"))
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
page := 1
|
|
||||||
if r.URL.Query().Has("page") {
|
|
||||||
page, err = strconv.Atoi(r.URL.Query().Get("page"))
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to convert page: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rankings, err := handleRankings(uuid, category, page)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := json.Marshal(rankings)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write(response)
|
|
||||||
case "/daily/rankingpagecount":
|
case "/daily/rankingpagecount":
|
||||||
var category int
|
s.handleRankingPageCount(w, r)
|
||||||
if r.URL.Query().Has("category") {
|
|
||||||
var err error
|
|
||||||
category, err = strconv.Atoi(r.URL.Query().Get("category"))
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := handleRankingPageCount(category)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Write([]byte(strconv.Itoa(count)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ func readSystemSaveData(uuid []byte) (defs.SystemSaveData, error) {
|
|||||||
return system, nil
|
return system, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSessionSaveData(uuid []byte, slotID int) (defs.SessionSaveData, error) {
|
func readSessionSaveData(uuid []byte, slotId int) (defs.SessionSaveData, error) {
|
||||||
var session defs.SessionSaveData
|
var session defs.SessionSaveData
|
||||||
|
|
||||||
fileName := "session"
|
fileName := "session"
|
||||||
if slotID != 0 {
|
if slotId != 0 {
|
||||||
fileName += strconv.Itoa(slotID)
|
fileName += strconv.Itoa(slotId)
|
||||||
}
|
}
|
||||||
|
|
||||||
save, err := os.ReadFile(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
save, err := os.ReadFile(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
||||||
|
|||||||
+186
-74
@@ -4,8 +4,10 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@@ -17,17 +19,25 @@ import (
|
|||||||
const sessionSlotCount = 3
|
const sessionSlotCount = 3
|
||||||
|
|
||||||
// /savedata/get - get save data
|
// /savedata/get - get save data
|
||||||
func handleSavedataGet(uuid []byte, datatype, slot int) (any, error) {
|
func (s *Server) handleSavedataGet(w http.ResponseWriter, r *http.Request) {
|
||||||
switch datatype {
|
uuid, err := getUuidFromRequest(r)
|
||||||
case 0: // System
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.URL.Query().Get("datatype") {
|
||||||
|
case "0": // System
|
||||||
system, err := readSystemSaveData(uuid)
|
system, err := readSystemSaveData(uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
httpError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
compensations, err := db.FetchAndClaimAccountCompensations(uuid)
|
compensations, err := db.FetchAndClaimAccountCompensations(uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to fetch compensations: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to fetch compensations: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range compensations {
|
for k, v := range compensations {
|
||||||
@@ -35,140 +45,215 @@ func handleSavedataGet(uuid []byte, datatype, slot int) (any, error) {
|
|||||||
system.VoucherCounts[typeKey] += v
|
system.VoucherCounts[typeKey] += v
|
||||||
}
|
}
|
||||||
|
|
||||||
return system, nil
|
saveJson, err := json.Marshal(system)
|
||||||
case 1: // Session
|
|
||||||
if slot < 0 || slot >= sessionSlotCount {
|
|
||||||
return nil, fmt.Errorf("slot id %d out of range", slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := readSessionSaveData(uuid, slot)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
httpError(w, r, fmt.Sprintf("failed to marshal save to json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return session, nil
|
w.Write(saveJson)
|
||||||
|
case "1": // Session
|
||||||
|
slotId, err := strconv.Atoi(r.URL.Query().Get("slot"))
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to convert slot id: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if slotId < 0 || slotId >= sessionSlotCount {
|
||||||
|
httpError(w, r, fmt.Sprintf("slot id %d out of range", slotId), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := readSessionSaveData(uuid, slotId)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
saveJson, err := json.Marshal(session)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal save to json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(saveJson)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid data type")
|
httpError(w, r, "invalid data type", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /savedata/update - update save data
|
// /savedata/update - update save data
|
||||||
func handleSavedataUpdate(uuid []byte, slot int, save any) error {
|
func (s *Server) handleSavedataUpdate(w http.ResponseWriter, r *http.Request) {
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
uuid, err := getUuidFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.UpdateAccountLastActivity(uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("failed to update account last activity")
|
log.Print("failed to update account last activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
hexUUID := hex.EncodeToString(uuid)
|
hexUuid := hex.EncodeToString(uuid)
|
||||||
|
|
||||||
switch save := save.(type) {
|
switch r.URL.Query().Get("datatype") {
|
||||||
case defs.SystemSaveData: // System
|
case "0": // System
|
||||||
if save.TrainerId == 0 && save.SecretId == 0 {
|
var system defs.SystemSaveData
|
||||||
return fmt.Errorf("invalid system data")
|
err = json.NewDecoder(r.Body).Decode(&system)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to decode request body: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = db.UpdateAccountStats(uuid, save.GameStats)
|
if system.TrainerId == 0 && system.SecretId == 0 {
|
||||||
|
httpError(w, r, "invalid system data", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.UpdateAccountStats(uuid, system.GameStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update account stats: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to update account stats: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var gobBuffer bytes.Buffer
|
var gobBuffer bytes.Buffer
|
||||||
err = gob.NewEncoder(&gobBuffer).Encode(save)
|
err = gob.NewEncoder(&gobBuffer).Encode(system)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to serialize save: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to serialize save: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zstdWriter, err := zstd.NewWriter(nil)
|
zstdWriter, err := zstd.NewWriter(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create zstd writer, %s", err)
|
httpError(w, r, fmt.Sprintf("failed to create zstd writer, %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
compressed := zstdWriter.EncodeAll(gobBuffer.Bytes(), nil)
|
compressed := zstdWriter.EncodeAll(gobBuffer.Bytes(), nil)
|
||||||
|
|
||||||
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
err = os.MkdirAll("userdata/"+hexUuid, 0755)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return fmt.Errorf("failed to create userdata folder: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to create userdata folder: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile("userdata/"+hexUUID+"/system.pzs", compressed, 0644)
|
err = os.WriteFile("userdata/"+hexUuid+"/system.pzs", compressed, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write save file: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to write save file: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
db.DeleteClaimedAccountCompensations(uuid)
|
db.DeleteClaimedAccountCompensations(uuid)
|
||||||
case defs.SessionSaveData: // Session
|
case "1": // Session
|
||||||
if slot < 0 || slot >= sessionSlotCount {
|
slotId, err := strconv.Atoi(r.URL.Query().Get("slot"))
|
||||||
return fmt.Errorf("slot id %d out of range", slot)
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to convert slot id: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if slotId < 0 || slotId >= sessionSlotCount {
|
||||||
|
httpError(w, r, fmt.Sprintf("slot id %d out of range", slotId), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := "session"
|
fileName := "session"
|
||||||
if slot != 0 {
|
if slotId != 0 {
|
||||||
fileName += strconv.Itoa(slot)
|
fileName += strconv.Itoa(slotId)
|
||||||
|
}
|
||||||
|
|
||||||
|
var session defs.SessionSaveData
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&session)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to decode request body: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var gobBuffer bytes.Buffer
|
var gobBuffer bytes.Buffer
|
||||||
err = gob.NewEncoder(&gobBuffer).Encode(save)
|
err = gob.NewEncoder(&gobBuffer).Encode(session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to serialize save: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to serialize save: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zstdWriter, err := zstd.NewWriter(nil)
|
zstdWriter, err := zstd.NewWriter(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create zstd writer, %s", err)
|
httpError(w, r, fmt.Sprintf("failed to create zstd writer, %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
compressed := zstdWriter.EncodeAll(gobBuffer.Bytes(), nil)
|
compressed := zstdWriter.EncodeAll(gobBuffer.Bytes(), nil)
|
||||||
|
|
||||||
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
err = os.MkdirAll("userdata/"+hexUuid, 0755)
|
||||||
if err != nil && !os.IsExist(err) {
|
if err != nil && !os.IsExist(err) {
|
||||||
return fmt.Errorf(fmt.Sprintf("failed to create userdata folder: %s", err))
|
httpError(w, r, fmt.Sprintf("failed to create userdata folder: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(fmt.Sprintf("userdata/%s/%s.pzs", hexUUID, fileName), compressed, 0644)
|
err = os.WriteFile(fmt.Sprintf("userdata/%s/%s.pzs", hexUuid, fileName), compressed, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write save file: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to write save file: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid data type")
|
httpError(w, r, "invalid data type", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
// /savedata/delete - delete save data
|
// /savedata/delete - delete save data
|
||||||
func handleSavedataDelete(uuid []byte, datatype, slot int) error {
|
func (s *Server) handleSavedataDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
uuid, err := getUuidFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.UpdateAccountLastActivity(uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("failed to update account last activity")
|
log.Print("failed to update account last activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
hexUUID := hex.EncodeToString(uuid)
|
hexUuid := hex.EncodeToString(uuid)
|
||||||
|
|
||||||
switch datatype {
|
switch r.URL.Query().Get("datatype") {
|
||||||
case 0: // System
|
case "0": // System
|
||||||
err := os.Remove("userdata/" + hexUUID + "/system.pzs")
|
err := os.Remove("userdata/" + hexUuid + "/system.pzs")
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("failed to delete save file: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to delete save file: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
case 1: // Session
|
case "1": // Session
|
||||||
if slot < 0 || slot >= sessionSlotCount {
|
slotId, err := strconv.Atoi(r.URL.Query().Get("slot"))
|
||||||
return fmt.Errorf("slot id %d out of range", slot)
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to convert slot id: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if slotId < 0 || slotId >= sessionSlotCount {
|
||||||
|
httpError(w, r, fmt.Sprintf("slot id %d out of range", slotId), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileName := "session"
|
fileName := "session"
|
||||||
if slot != 0 {
|
if slotId != 0 {
|
||||||
fileName += strconv.Itoa(slot)
|
fileName += strconv.Itoa(slotId)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hexUUID, fileName))
|
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hexUuid, fileName))
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("failed to delete save file: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to delete save file: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid data type")
|
httpError(w, r, "invalid data type", http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
w.WriteHeader(http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedataClearResponse struct {
|
type SavedataClearResponse struct {
|
||||||
@@ -176,46 +261,73 @@ type SavedataClearResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// /savedata/clear - mark session save data as cleared and delete
|
// /savedata/clear - mark session save data as cleared and delete
|
||||||
func handleSavedataClear(uuid []byte, slot int, save defs.SessionSaveData) (SavedataClearResponse, error) {
|
func (s *Server) handleSavedataClear(w http.ResponseWriter, r *http.Request) {
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
uuid, err := getUuidFromRequest(r)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.UpdateAccountLastActivity(uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print("failed to update account last activity")
|
log.Print("failed to update account last activity")
|
||||||
}
|
}
|
||||||
|
|
||||||
if slot < 0 || slot >= sessionSlotCount {
|
slotId, err := strconv.Atoi(r.URL.Query().Get("slot"))
|
||||||
return SavedataClearResponse{}, fmt.Errorf("slot id %d out of range", slot)
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to convert slot id: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionCompleted := validateSessionCompleted(save)
|
if slotId < 0 || slotId >= sessionSlotCount {
|
||||||
|
httpError(w, r, fmt.Sprintf("slot id %d out of range", slotId), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var session defs.SessionSaveData
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&session)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to decode request body: %s", err), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCompleted := validateSessionCompleted(session)
|
||||||
newCompletion := false
|
newCompletion := false
|
||||||
|
|
||||||
if save.GameMode == 3 && save.Seed == dailyRunSeed {
|
if session.GameMode == 3 && session.Seed == dailyRunSeed {
|
||||||
waveCompleted := save.WaveIndex
|
waveCompleted := session.WaveIndex
|
||||||
if !sessionCompleted {
|
if !sessionCompleted {
|
||||||
waveCompleted--
|
waveCompleted--
|
||||||
}
|
}
|
||||||
err = db.AddOrUpdateAccountDailyRun(uuid, save.Score, waveCompleted)
|
err = db.AddOrUpdateAccountDailyRun(uuid, session.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionCompleted {
|
if sessionCompleted {
|
||||||
newCompletion, err = db.TryAddSeedCompletion(uuid, save.Seed, int(save.GameMode))
|
newCompletion, err = db.TryAddSeedCompletion(uuid, session.Seed, int(session.GameMode))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to mark seed as completed: %s", err)
|
log.Printf("failed to mark seed as completed: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
response, err := json.Marshal(SavedataClearResponse{Success: newCompletion})
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, r, fmt.Sprintf("failed to marshal response json: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
fileName := "session"
|
fileName := "session"
|
||||||
if slot != 0 {
|
if slotId != 0 {
|
||||||
fileName += strconv.Itoa(slot)
|
fileName += strconv.Itoa(slotId)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
||||||
if err != nil && !os.IsNotExist(err) {
|
if err != nil && !os.IsNotExist(err) {
|
||||||
return SavedataClearResponse{}, fmt.Errorf("failed to delete save file: %s", err)
|
httpError(w, r, fmt.Sprintf("failed to delete save file: %s", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return SavedataClearResponse{Success: newCompletion}, nil
|
w.Write(response)
|
||||||
}
|
}
|
||||||
+1
-1
@@ -157,7 +157,7 @@ func FetchAccountKeySaltFromUsername(username string) ([]byte, []byte, error) {
|
|||||||
return key, salt, nil
|
return key, salt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FetchUUIDFromToken(token []byte) ([]byte, error) {
|
func FetchUuidFromToken(token []byte) ([]byte, error) {
|
||||||
var uuid []byte
|
var uuid []byte
|
||||||
err := handle.QueryRow("SELECT uuid FROM sessions WHERE token = ? AND expire > UTC_TIMESTAMP()", token).Scan(&uuid)
|
err := handle.QueryRow("SELECT uuid FROM sessions WHERE token = ? AND expire > UTC_TIMESTAMP()", token).Scan(&uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in new issue
Block a user