Move endpoint categories into their own packages
parent
522ce9f4fa
commit
1f0f38d38e
@ -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)
|
|
||||||
}
|
|
@ -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
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue