Move endpoint categories into their own packages
parent
522ce9f4fa
commit
1f0f38d38e
141
api/account.go
141
api/account.go
|
@ -1,141 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
UUIDSize = 16
|
|
||||||
TokenSize = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
var isValidUsername = regexp.MustCompile(`^\w{1,16}$`).MatchString
|
|
||||||
|
|
||||||
type AccountInfoResponse struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
LastSessionSlot int `json:"lastSessionSlot"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// /account/info - get account info
|
|
||||||
func handleAccountInfo(username string, uuid []byte) (AccountInfoResponse, error) {
|
|
||||||
var latestSave time.Time
|
|
||||||
latestSaveID := -1
|
|
||||||
for id := range sessionSlotCount {
|
|
||||||
fileName := "session"
|
|
||||||
if id != 0 {
|
|
||||||
fileName += strconv.Itoa(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := os.Stat(fmt.Sprintf("userdata/%x/%s.pzs", uuid, fileName))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat.ModTime().After(latestSave) {
|
|
||||||
latestSave = stat.ModTime()
|
|
||||||
latestSaveID = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return AccountInfoResponse{Username: username, LastSessionSlot: latestSaveID}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountRegisterRequest GenericAuthRequest
|
|
||||||
|
|
||||||
// /account/register - register account
|
|
||||||
func handleAccountRegister(request AccountRegisterRequest) error {
|
|
||||||
if !isValidUsername(request.Username) {
|
|
||||||
return fmt.Errorf("invalid username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(request.Password) < 6 {
|
|
||||||
return fmt.Errorf("invalid password")
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid := make([]byte, UUIDSize)
|
|
||||||
_, err := rand.Read(uuid)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to generate uuid: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
salt := make([]byte, ArgonSaltSize)
|
|
||||||
_, err = rand.Read(salt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("failed to generate salt: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddAccountRecord(uuid, request.Username, deriveArgon2IDKey([]byte(request.Password), salt), salt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to add account record: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type AccountLoginRequest GenericAuthRequest
|
|
||||||
type AccountLoginResponse GenericAuthResponse
|
|
||||||
|
|
||||||
// /account/login - log into account
|
|
||||||
func handleAccountLogin(request AccountLoginRequest) (AccountLoginResponse, error) {
|
|
||||||
if !isValidUsername(request.Username) {
|
|
||||||
return AccountLoginResponse{}, fmt.Errorf("invalid username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(request.Password) < 6 {
|
|
||||||
return AccountLoginResponse{}, fmt.Errorf("invalid password")
|
|
||||||
}
|
|
||||||
|
|
||||||
key, salt, err := db.FetchAccountKeySaltFromUsername(request.Username)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return AccountLoginResponse{}, fmt.Errorf("account doesn't exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
return AccountLoginResponse{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(key, deriveArgon2IDKey([]byte(request.Password), salt)) {
|
|
||||||
return AccountLoginResponse{}, fmt.Errorf("password doesn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
token := make([]byte, TokenSize)
|
|
||||||
_, err = rand.Read(token)
|
|
||||||
if err != nil {
|
|
||||||
return AccountLoginResponse{}, fmt.Errorf("failed to generate token: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddAccountSession(request.Username, token)
|
|
||||||
if err != nil {
|
|
||||||
return AccountLoginResponse{}, fmt.Errorf("failed to add account session")
|
|
||||||
}
|
|
||||||
|
|
||||||
return AccountLoginResponse{Token: base64.StdEncoding.EncodeToString(token)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// /account/logout - log out of account
|
|
||||||
func handleAccountLogout(token []byte) error {
|
|
||||||
if len(token) != TokenSize {
|
|
||||||
return fmt.Errorf("invalid token")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := db.RemoveSessionFromToken(token)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return fmt.Errorf("token not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("failed to remove account session")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GenericAuthRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericAuthResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ArgonTime = 1
|
||||||
|
ArgonMemory = 256 * 1024
|
||||||
|
ArgonThreads = 4
|
||||||
|
ArgonKeySize = 32
|
||||||
|
ArgonSaltSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
var isValidUsername = regexp.MustCompile(`^\w{1,16}$`).MatchString
|
||||||
|
|
||||||
|
func deriveArgon2IDKey(password, salt []byte) []byte {
|
||||||
|
return argon2.IDKey(password, salt, ArgonTime, ArgonMemory, ArgonThreads, ArgonKeySize)
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InfoResponse struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
LastSessionSlot int `json:"lastSessionSlot"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// /account/info - get account info
|
||||||
|
func Info(username string, uuid []byte) (InfoResponse, error) {
|
||||||
|
var latestSave time.Time
|
||||||
|
latestSaveID := -1
|
||||||
|
for id := range defs.SessionSlotCount {
|
||||||
|
fileName := "session"
|
||||||
|
if id != 0 {
|
||||||
|
fileName += strconv.Itoa(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := os.Stat(fmt.Sprintf("userdata/%x/%s.pzs", uuid, fileName))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if stat.ModTime().After(latestSave) {
|
||||||
|
latestSave = stat.ModTime()
|
||||||
|
latestSaveID = id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InfoResponse{Username: username, LastSessionSlot: latestSaveID}, nil
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginRequest GenericAuthRequest
|
||||||
|
type LoginResponse GenericAuthResponse
|
||||||
|
|
||||||
|
// /account/login - log into account
|
||||||
|
func Login(request LoginRequest) (LoginResponse, error) {
|
||||||
|
if !isValidUsername(request.Username) {
|
||||||
|
return LoginResponse{}, fmt.Errorf("invalid username")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(request.Password) < 6 {
|
||||||
|
return LoginResponse{}, fmt.Errorf("invalid password")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, salt, err := db.FetchAccountKeySaltFromUsername(request.Username)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return LoginResponse{}, fmt.Errorf("account doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoginResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(key, deriveArgon2IDKey([]byte(request.Password), salt)) {
|
||||||
|
return LoginResponse{}, fmt.Errorf("password doesn't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
token := make([]byte, TokenSize)
|
||||||
|
_, err = rand.Read(token)
|
||||||
|
if err != nil {
|
||||||
|
return LoginResponse{}, fmt.Errorf("failed to generate token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.AddAccountSession(request.Username, token)
|
||||||
|
if err != nil {
|
||||||
|
return LoginResponse{}, fmt.Errorf("failed to add account session")
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoginResponse{Token: base64.StdEncoding.EncodeToString(token)}, nil
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /account/logout - log out of account
|
||||||
|
func Logout(token []byte) error {
|
||||||
|
if len(token) != TokenSize {
|
||||||
|
return fmt.Errorf("invalid token")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := db.RemoveSessionFromToken(token)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return fmt.Errorf("token not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to remove account session")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package account
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UUIDSize = 16
|
||||||
|
TokenSize = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
type RegisterRequest GenericAuthRequest
|
||||||
|
|
||||||
|
// /account/register - register account
|
||||||
|
func Register(request RegisterRequest) error {
|
||||||
|
if !isValidUsername(request.Username) {
|
||||||
|
return fmt.Errorf("invalid username")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(request.Password) < 6 {
|
||||||
|
return fmt.Errorf("invalid password")
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid := make([]byte, UUIDSize)
|
||||||
|
_, err := rand.Read(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate uuid: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, ArgonSaltSize)
|
||||||
|
_, err = rand.Read(salt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("failed to generate salt: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.AddAccountRecord(uuid, request.Username, deriveArgon2IDKey([]byte(request.Password), salt), salt)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to add account record: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,15 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import "golang.org/x/crypto/argon2"
|
|
||||||
|
|
||||||
const (
|
|
||||||
ArgonTime = 1
|
|
||||||
ArgonMemory = 256 * 1024
|
|
||||||
ArgonThreads = 4
|
|
||||||
ArgonKeySize = 32
|
|
||||||
ArgonSaltSize = 16
|
|
||||||
)
|
|
||||||
|
|
||||||
func deriveArgon2IDKey(password, salt []byte) []byte {
|
|
||||||
return argon2.IDKey(password, salt, ArgonTime, ArgonMemory, ArgonThreads, ArgonKeySize)
|
|
||||||
}
|
|
|
@ -5,9 +5,15 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/api/daily"
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
scheduleStatRefresh()
|
||||||
|
daily.Init()
|
||||||
|
}
|
||||||
|
|
||||||
func getUsernameFromRequest(r *http.Request) (string, error) {
|
func getUsernameFromRequest(r *http.Request) (string, error) {
|
||||||
if r.Header.Get("Authorization") == "" {
|
if r.Header.Get("Authorization") == "" {
|
||||||
return "", fmt.Errorf("missing token")
|
return "", fmt.Errorf("missing token")
|
|
@ -1,4 +1,4 @@
|
||||||
package api
|
package daily
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
@ -12,7 +12,6 @@ import (
|
||||||
|
|
||||||
"github.com/go-co-op/gocron"
|
"github.com/go-co-op/gocron"
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
"github.com/pagefaultgames/pokerogue-server/defs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const secondsPerDay = 60 * 60 * 24
|
const secondsPerDay = 60 * 60 * 24
|
||||||
|
@ -20,22 +19,9 @@ const secondsPerDay = 60 * 60 * 24
|
||||||
var (
|
var (
|
||||||
dailyRunScheduler = gocron.NewScheduler(time.UTC)
|
dailyRunScheduler = gocron.NewScheduler(time.UTC)
|
||||||
dailyRunSecret []byte
|
dailyRunSecret []byte
|
||||||
dailyRunSeed string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ScheduleDailyRunRefresh() {
|
func Init() error {
|
||||||
dailyRunScheduler.Every(1).Day().At("00:00").Do(func() error {
|
|
||||||
err := InitDailyRun()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}())
|
|
||||||
dailyRunScheduler.StartAsync()
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitDailyRun() error {
|
|
||||||
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) {
|
||||||
|
@ -58,18 +44,34 @@ func InitDailyRun() error {
|
||||||
|
|
||||||
dailyRunSecret = secret
|
dailyRunSecret = secret
|
||||||
|
|
||||||
dailyRunSeed = base64.StdEncoding.EncodeToString(deriveDailyRunSeed(time.Now().UTC()))
|
err = db.TryAddDailyRun(Seed())
|
||||||
|
|
||||||
err = db.TryAddDailyRun(dailyRunSeed)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
log.Print(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Daily Run Seed: %s", dailyRunSeed)
|
log.Printf("Daily Run Seed: %s", Seed())
|
||||||
|
|
||||||
|
scheduleRefresh()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Seed() string {
|
||||||
|
return base64.StdEncoding.EncodeToString(deriveDailyRunSeed(time.Now().UTC()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func scheduleRefresh() {
|
||||||
|
dailyRunScheduler.Every(1).Day().At("00:00").Do(func() error {
|
||||||
|
err := Init()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}())
|
||||||
|
dailyRunScheduler.StartAsync()
|
||||||
|
}
|
||||||
|
|
||||||
func deriveDailyRunSeed(seedTime time.Time) []byte {
|
func deriveDailyRunSeed(seedTime time.Time) []byte {
|
||||||
day := make([]byte, 8)
|
day := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(day, uint64(seedTime.Unix()/secondsPerDay))
|
binary.BigEndian.PutUint64(day, uint64(seedTime.Unix()/secondsPerDay))
|
||||||
|
@ -78,28 +80,3 @@ func deriveDailyRunSeed(seedTime time.Time) []byte {
|
||||||
|
|
||||||
return hashedSeed[:]
|
return hashedSeed[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// /daily/rankings - fetch daily rankings
|
|
||||||
func handleRankings(uuid []byte, category, page int) ([]defs.DailyRanking, error) {
|
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to update account last activity")
|
|
||||||
}
|
|
||||||
|
|
||||||
rankings, err := db.FetchRankings(category, page)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to retrieve rankings")
|
|
||||||
}
|
|
||||||
|
|
||||||
return rankings, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// /daily/rankingpagecount - fetch daily ranking page count
|
|
||||||
func handleRankingPageCount(category int) (int, error) {
|
|
||||||
pageCount, err := db.FetchRankingPageCount(category)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to retrieve ranking page count")
|
|
||||||
}
|
|
||||||
|
|
||||||
return pageCount, nil
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package daily
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /daily/rankings - fetch daily rankings
|
||||||
|
func Rankings(uuid []byte, category, page int) ([]defs.DailyRanking, error) {
|
||||||
|
err := db.UpdateAccountLastActivity(uuid)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("failed to update account last activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
rankings, err := db.FetchRankings(category, page)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("failed to retrieve rankings")
|
||||||
|
}
|
||||||
|
|
||||||
|
return rankings, nil
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package daily
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /daily/rankingpagecount - fetch daily ranking page count
|
||||||
|
func RankingPageCount(category int) (int, error) {
|
||||||
|
pageCount, err := db.FetchRankingPageCount(category)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("failed to retrieve ranking page count")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageCount, nil
|
||||||
|
}
|
|
@ -9,6 +9,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/api/account"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/api/daily"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/api/savedata"
|
||||||
"github.com/pagefaultgames/pokerogue-server/defs"
|
"github.com/pagefaultgames/pokerogue-server/defs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -52,7 +55,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := handleAccountInfo(username, uuid)
|
response, err := account.Info(username, uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
httpError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -64,14 +67,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "/account/register":
|
case "/account/register":
|
||||||
var request AccountRegisterRequest
|
var request account.RegisterRequest
|
||||||
err := json.NewDecoder(r.Body).Decode(&request)
|
err := json.NewDecoder(r.Body).Decode(&request)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleAccountRegister(request)
|
err = account.Register(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
httpError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -79,14 +82,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
case "/account/login":
|
case "/account/login":
|
||||||
var request AccountLoginRequest
|
var request account.LoginRequest
|
||||||
err := json.NewDecoder(r.Body).Decode(&request)
|
err := json.NewDecoder(r.Body).Decode(&request)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := handleAccountLogin(request)
|
response, err := account.Login(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
httpError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -104,7 +107,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handleAccountLogout(token)
|
err = account.Logout(token)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
httpError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -180,14 +183,14 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
switch r.URL.Path {
|
switch r.URL.Path {
|
||||||
case "/savedata/get":
|
case "/savedata/get":
|
||||||
save, err = handleSavedataGet(uuid, datatype, slot)
|
save, err = savedata.Get(uuid, datatype, slot)
|
||||||
case "/savedata/update":
|
case "/savedata/update":
|
||||||
err = handleSavedataUpdate(uuid, slot, save)
|
err = savedata.Update(uuid, slot, save)
|
||||||
case "/savedata/delete":
|
case "/savedata/delete":
|
||||||
err = handleSavedataDelete(uuid, datatype, slot)
|
err = savedata.Delete(uuid, datatype, slot)
|
||||||
case "/savedata/clear":
|
case "/savedata/clear":
|
||||||
// doesn't return a save, but it works
|
// doesn't return a save, but it works
|
||||||
save, err = handleSavedataClear(uuid, slot, save.(defs.SessionSaveData))
|
save, err = savedata.Clear(uuid, slot, daily.Seed(), save.(defs.SessionSaveData))
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
httpError(w, r, err, http.StatusInternalServerError)
|
||||||
|
@ -207,7 +210,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// /daily
|
// /daily
|
||||||
case "/daily/seed":
|
case "/daily/seed":
|
||||||
w.Write([]byte(dailyRunSeed))
|
w.Write([]byte(daily.Seed()))
|
||||||
case "/daily/rankings":
|
case "/daily/rankings":
|
||||||
uuid, err := getUUIDFromRequest(r)
|
uuid, err := getUUIDFromRequest(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -233,7 +236,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rankings, err := handleRankings(uuid, category, page)
|
rankings, err := daily.Rankings(uuid, category, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
httpError(w, r, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -255,7 +258,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count, err := handleRankingPageCount(category)
|
count, err := daily.RankingPageCount(category)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, r, err, http.StatusInternalServerError)
|
httpError(w, r, err, http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
@ -268,14 +271,3 @@ func httpError(w http.ResponseWriter, r *http.Request, err error, code int) {
|
||||||
log.Printf("%s: %s\n", r.URL.Path, err)
|
log.Printf("%s: %s\n", r.URL.Path, err)
|
||||||
http.Error(w, err.Error(), code)
|
http.Error(w, err.Error(), code)
|
||||||
}
|
}
|
||||||
|
|
||||||
// auth
|
|
||||||
|
|
||||||
type GenericAuthRequest struct {
|
|
||||||
Username string `json:"username"`
|
|
||||||
Password string `json:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type GenericAuthResponse struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
222
api/savedata.go
222
api/savedata.go
|
@ -1,222 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/klauspost/compress/zstd"
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/defs"
|
|
||||||
)
|
|
||||||
|
|
||||||
const sessionSlotCount = 3
|
|
||||||
|
|
||||||
// /savedata/get - get save data
|
|
||||||
func handleSavedataGet(uuid []byte, datatype, slot int) (any, error) {
|
|
||||||
switch datatype {
|
|
||||||
case 0: // System
|
|
||||||
system, err := readSystemSaveData(uuid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
compensations, err := db.FetchAndClaimAccountCompensations(uuid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to fetch compensations: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range compensations {
|
|
||||||
typeKey := strconv.Itoa(k)
|
|
||||||
system.VoucherCounts[typeKey] += v
|
|
||||||
}
|
|
||||||
|
|
||||||
return system, nil
|
|
||||||
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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return session, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid data type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /savedata/update - update save data
|
|
||||||
func handleSavedataUpdate(uuid []byte, slot int, save any) error {
|
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to update account last activity")
|
|
||||||
}
|
|
||||||
|
|
||||||
hexUUID := hex.EncodeToString(uuid)
|
|
||||||
|
|
||||||
switch save := save.(type) {
|
|
||||||
case defs.SystemSaveData: // System
|
|
||||||
if save.TrainerId == 0 && save.SecretId == 0 {
|
|
||||||
return fmt.Errorf("invalid system data")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.UpdateAccountStats(uuid, save.GameStats, save.VoucherCounts)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to update account stats: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
return fmt.Errorf("failed to create userdata folder: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile("userdata/"+hexUUID+"/system.pzs", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open save file for writing: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
zstdEncoder, err := zstd.NewWriter(file)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create zstd encoder: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer zstdEncoder.Close()
|
|
||||||
|
|
||||||
err = gob.NewEncoder(zstdEncoder).Encode(save)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to serialize save: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
db.DeleteClaimedAccountCompensations(uuid)
|
|
||||||
case defs.SessionSaveData: // Session
|
|
||||||
if slot < 0 || slot >= sessionSlotCount {
|
|
||||||
return fmt.Errorf("slot id %d out of range", slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := "session"
|
|
||||||
if slot != 0 {
|
|
||||||
fileName += strconv.Itoa(slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
|
||||||
if err != nil && !os.IsExist(err) {
|
|
||||||
return fmt.Errorf(fmt.Sprintf("failed to create userdata folder: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.OpenFile(fmt.Sprintf("userdata/%s/%s.pzs", hexUUID, fileName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to open save file for writing: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
zstdEncoder, err := zstd.NewWriter(file)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create zstd encoder: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer zstdEncoder.Close()
|
|
||||||
|
|
||||||
err = gob.NewEncoder(zstdEncoder).Encode(save)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to serialize save: %s", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid data type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// /savedata/delete - delete save data
|
|
||||||
func handleSavedataDelete(uuid []byte, datatype, slot int) error {
|
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to update account last activity")
|
|
||||||
}
|
|
||||||
|
|
||||||
hexUUID := hex.EncodeToString(uuid)
|
|
||||||
|
|
||||||
switch datatype {
|
|
||||||
case 0: // System
|
|
||||||
err := os.Remove("userdata/" + hexUUID + "/system.pzs")
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("failed to delete save file: %s", err)
|
|
||||||
}
|
|
||||||
case 1: // Session
|
|
||||||
if slot < 0 || slot >= sessionSlotCount {
|
|
||||||
return fmt.Errorf("slot id %d out of range", slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := "session"
|
|
||||||
if slot != 0 {
|
|
||||||
fileName += strconv.Itoa(slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hexUUID, fileName))
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("failed to delete save file: %s", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("invalid data type")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type SavedataClearResponse struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// /savedata/clear - mark session save data as cleared and delete
|
|
||||||
func handleSavedataClear(uuid []byte, slot int, save defs.SessionSaveData) (SavedataClearResponse, error) {
|
|
||||||
var response SavedataClearResponse
|
|
||||||
err := db.UpdateAccountLastActivity(uuid)
|
|
||||||
if err != nil {
|
|
||||||
log.Print("failed to update account last activity")
|
|
||||||
}
|
|
||||||
|
|
||||||
if slot < 0 || slot >= sessionSlotCount {
|
|
||||||
return response, fmt.Errorf("slot id %d out of range", slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionCompleted := validateSessionCompleted(save)
|
|
||||||
|
|
||||||
if save.GameMode == 3 && save.Seed == dailyRunSeed {
|
|
||||||
waveCompleted := save.WaveIndex
|
|
||||||
if !sessionCompleted {
|
|
||||||
waveCompleted--
|
|
||||||
}
|
|
||||||
err = db.AddOrUpdateAccountDailyRun(uuid, save.Score, waveCompleted)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to add or update daily run record: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sessionCompleted {
|
|
||||||
response.Success, err = db.TryAddSeedCompletion(uuid, save.Seed, int(save.GameMode))
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to mark seed as completed: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := "session"
|
|
||||||
if slot != 0 {
|
|
||||||
fileName += strconv.Itoa(slot)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return response, fmt.Errorf("failed to delete save file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
package savedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClearResponse struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// /savedata/clear - mark session save data as cleared and delete
|
||||||
|
func Clear(uuid []byte, slot int, seed string, save defs.SessionSaveData) (ClearResponse, error) {
|
||||||
|
var response ClearResponse
|
||||||
|
err := db.UpdateAccountLastActivity(uuid)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("failed to update account last activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
if slot < 0 || slot >= defs.SessionSlotCount {
|
||||||
|
return response, fmt.Errorf("slot id %d out of range", slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCompleted := validateSessionCompleted(save)
|
||||||
|
|
||||||
|
if save.GameMode == 3 && save.Seed == seed {
|
||||||
|
waveCompleted := save.WaveIndex
|
||||||
|
if !sessionCompleted {
|
||||||
|
waveCompleted--
|
||||||
|
}
|
||||||
|
err = db.AddOrUpdateAccountDailyRun(uuid, save.Score, waveCompleted)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to add or update daily run record: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessionCompleted {
|
||||||
|
response.Success, err = db.TryAddSeedCompletion(uuid, save.Seed, int(save.GameMode))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("failed to mark seed as completed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := "session"
|
||||||
|
if slot != 0 {
|
||||||
|
fileName += strconv.Itoa(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return response, fmt.Errorf("failed to delete save file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, nil
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package api
|
package savedata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
|
@ -0,0 +1,48 @@
|
||||||
|
package savedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /savedata/delete - delete save data
|
||||||
|
func Delete(uuid []byte, datatype, slot int) error {
|
||||||
|
err := db.UpdateAccountLastActivity(uuid)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("failed to update account last activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
hexUUID := hex.EncodeToString(uuid)
|
||||||
|
|
||||||
|
switch datatype {
|
||||||
|
case 0: // System
|
||||||
|
err := os.Remove("userdata/" + hexUUID + "/system.pzs")
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("failed to delete save file: %s", err)
|
||||||
|
}
|
||||||
|
case 1: // Session
|
||||||
|
if slot < 0 || slot >= defs.SessionSlotCount {
|
||||||
|
return fmt.Errorf("slot id %d out of range", slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := "session"
|
||||||
|
if slot != 0 {
|
||||||
|
fileName += strconv.Itoa(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(fmt.Sprintf("userdata/%s/%s.pzs", hexUUID, fileName))
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("failed to delete save file: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid data type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package savedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /savedata/get - get save data
|
||||||
|
func Get(uuid []byte, datatype, slot int) (any, error) {
|
||||||
|
switch datatype {
|
||||||
|
case 0: // System
|
||||||
|
system, err := readSystemSaveData(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
compensations, err := db.FetchAndClaimAccountCompensations(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch compensations: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range compensations {
|
||||||
|
typeKey := strconv.Itoa(k)
|
||||||
|
system.VoucherCounts[typeKey] += v
|
||||||
|
}
|
||||||
|
|
||||||
|
return system, nil
|
||||||
|
case 1: // Session
|
||||||
|
if slot < 0 || slot >= defs.SessionSlotCount {
|
||||||
|
return nil, fmt.Errorf("slot id %d out of range", slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := readSessionSaveData(uuid, slot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return session, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid data type")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package savedata
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/klauspost/compress/zstd"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/db"
|
||||||
|
"github.com/pagefaultgames/pokerogue-server/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// /savedata/update - update save data
|
||||||
|
func Update(uuid []byte, slot int, save any) error {
|
||||||
|
err := db.UpdateAccountLastActivity(uuid)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("failed to update account last activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
hexUUID := hex.EncodeToString(uuid)
|
||||||
|
|
||||||
|
switch save := save.(type) {
|
||||||
|
case defs.SystemSaveData: // System
|
||||||
|
if save.TrainerId == 0 && save.SecretId == 0 {
|
||||||
|
return fmt.Errorf("invalid system data")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.UpdateAccountStats(uuid, save.GameStats, save.VoucherCounts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update account stats: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return fmt.Errorf("failed to create userdata folder: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile("userdata/"+hexUUID+"/system.pzs", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open save file for writing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
zstdEncoder, err := zstd.NewWriter(file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create zstd encoder: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer zstdEncoder.Close()
|
||||||
|
|
||||||
|
err = gob.NewEncoder(zstdEncoder).Encode(save)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to serialize save: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.DeleteClaimedAccountCompensations(uuid)
|
||||||
|
case defs.SessionSaveData: // Session
|
||||||
|
if slot < 0 || slot >= defs.SessionSlotCount {
|
||||||
|
return fmt.Errorf("slot id %d out of range", slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileName := "session"
|
||||||
|
if slot != 0 {
|
||||||
|
fileName += strconv.Itoa(slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("failed to create userdata folder: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(fmt.Sprintf("userdata/%s/%s.pzs", hexUUID, fileName), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to open save file for writing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
zstdEncoder, err := zstd.NewWriter(file)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create zstd encoder: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer zstdEncoder.Close()
|
||||||
|
|
||||||
|
err = gob.NewEncoder(zstdEncoder).Encode(save)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to serialize save: %s", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid data type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ var (
|
||||||
classicSessionCount int
|
classicSessionCount int
|
||||||
)
|
)
|
||||||
|
|
||||||
func ScheduleStatRefresh() {
|
func scheduleStatRefresh() {
|
||||||
statScheduler.Every(10).Second().Do(updateStats)
|
statScheduler.Every(10).Second().Do(updateStats)
|
||||||
statScheduler.StartAsync()
|
statScheduler.StartAsync()
|
||||||
}
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
package defs
|
package defs
|
||||||
|
|
||||||
|
const SessionSlotCount = 3
|
||||||
|
|
||||||
type SystemSaveData struct {
|
type SystemSaveData struct {
|
||||||
TrainerId int `json:"trainerId"`
|
TrainerId int `json:"trainerId"`
|
||||||
SecretId int `json:"secretId"`
|
SecretId int `json:"secretId"`
|
||||||
|
|
|
@ -43,9 +43,7 @@ func main() {
|
||||||
os.Chmod(*addr, 0777)
|
os.Chmod(*addr, 0777)
|
||||||
}
|
}
|
||||||
|
|
||||||
api.ScheduleStatRefresh()
|
api.Init()
|
||||||
api.ScheduleDailyRunRefresh()
|
|
||||||
api.InitDailyRun()
|
|
||||||
|
|
||||||
err = http.Serve(listener, &api.Server{Debug: *debug})
|
err = http.Serve(listener, &api.Server{Debug: *debug})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
Loading…
Reference in New Issue