Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2547ca173a |
No files matched your search
@@ -1,28 +0,0 @@
|
|||||||
# This workflow will build a golang project
|
|
||||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
|
|
||||||
|
|
||||||
name: Go
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ "master" ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ "master" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Go
|
|
||||||
uses: actions/setup-go@v4
|
|
||||||
with:
|
|
||||||
go-version: '1.20'
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: go build -v ./...
|
|
||||||
|
|
||||||
- name: Test
|
|
||||||
run: go test -v ./...
|
|
||||||
+1
-3
@@ -1,6 +1,4 @@
|
|||||||
|
|
||||||
# no extension on linux, .exe on windows
|
pokerogue-server.exe
|
||||||
pokerogue-server*
|
|
||||||
userdata/*
|
userdata/*
|
||||||
secret.key
|
secret.key
|
||||||
www/
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getUsernameFromRequest(r *http.Request) (string, error) {
|
||||||
|
if r.Header.Get("Authorization") == "" {
|
||||||
|
return "", fmt.Errorf("missing token")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := base64.StdEncoding.DecodeString(r.Header.Get("Authorization"))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to decode token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(token) != 32 {
|
||||||
|
return "", fmt.Errorf("invalid token length: got %d, expected 32", len(token))
|
||||||
|
}
|
||||||
|
|
||||||
|
username, err := db.FetchUsernameFromToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to validate token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return username, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUUIDFromRequest(r *http.Request) ([]byte, error) {
|
||||||
|
if r.Header.Get("Authorization") == "" {
|
||||||
|
return nil, fmt.Errorf("missing token")
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := base64.StdEncoding.DecodeString(r.Header.Get("Authorization"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decode token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(token) != 32 {
|
||||||
|
return nil, fmt.Errorf("invalid token length: got %d, expected 32", len(token))
|
||||||
|
}
|
||||||
|
|
||||||
|
uuid, err := db.FetchUUIDFromToken(token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to validate token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uuid, nil
|
||||||
|
}
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
UUIDSize = 16
|
||||||
|
ArgonTime = 1
|
||||||
|
ArgonMemory = 256 * 1024
|
||||||
|
ArgonThreads = 4
|
||||||
|
ArgonKeySize = 32
|
||||||
|
ArgonSaltSize = 16
|
||||||
|
)
|
||||||
|
|
||||||
|
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(username, password string) error {
|
||||||
|
if !isValidUsername(username) {
|
||||||
|
return fmt.Errorf("invalid username")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(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, username, argon2.IDKey([]byte(password), salt, ArgonTime, ArgonMemory, ArgonThreads, ArgonKeySize), 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(username, password string) (AccountLoginResponse, error) {
|
||||||
|
if !isValidUsername(username) {
|
||||||
|
return AccountLoginResponse{}, fmt.Errorf("invalid username")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(password) < 6 {
|
||||||
|
return AccountLoginResponse{}, fmt.Errorf("invalid password")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, salt, err := db.FetchAccountKeySaltFromUsername(username)
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return AccountLoginResponse{}, fmt.Errorf("account doesn't exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountLoginResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(key, argon2.IDKey([]byte(password), salt, ArgonTime, ArgonMemory, ArgonThreads, ArgonKeySize)) {
|
||||||
|
return AccountLoginResponse{}, fmt.Errorf("password doesn't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
token := make([]byte, 32)
|
||||||
|
_, err = rand.Read(token)
|
||||||
|
if err != nil {
|
||||||
|
return AccountLoginResponse{}, fmt.Errorf("failed to generate token: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.AddAccountSession(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) != 32 {
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package account
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/argon2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GenericAuthResponse struct {
|
|
||||||
Token string `json:"token"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
ArgonTime = 1
|
|
||||||
ArgonMemory = 256 * 1024
|
|
||||||
ArgonThreads = 4
|
|
||||||
ArgonKeySize = 32
|
|
||||||
ArgonSaltSize = 16
|
|
||||||
|
|
||||||
UUIDSize = 16
|
|
||||||
TokenSize = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
var isValidUsername = regexp.MustCompile(`^\w{1,16}$`).MatchString
|
|
||||||
|
|
||||||
func deriveArgon2IDKey(password, salt []byte) []byte {
|
|
||||||
return argon2.IDKey(password, salt, ArgonTime, ArgonMemory, ArgonThreads, ArgonKeySize)
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package account
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LoginResponse GenericAuthResponse
|
|
||||||
|
|
||||||
// /account/login - log into account
|
|
||||||
func Login(username, password string) (LoginResponse, error) {
|
|
||||||
var response LoginResponse
|
|
||||||
|
|
||||||
if !isValidUsername(username) {
|
|
||||||
return response, fmt.Errorf("invalid username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(password) < 6 {
|
|
||||||
return response, fmt.Errorf("invalid password")
|
|
||||||
}
|
|
||||||
|
|
||||||
key, salt, err := db.FetchAccountKeySaltFromUsername(username)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return response, fmt.Errorf("account doesn't exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(key, deriveArgon2IDKey([]byte(password), salt)) {
|
|
||||||
return response, fmt.Errorf("password doesn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
token := make([]byte, TokenSize)
|
|
||||||
_, err = rand.Read(token)
|
|
||||||
if err != nil {
|
|
||||||
return response, fmt.Errorf("failed to generate token: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.AddAccountSession(username, token)
|
|
||||||
if err != nil {
|
|
||||||
return response, fmt.Errorf("failed to add account session")
|
|
||||||
}
|
|
||||||
|
|
||||||
response.Token = base64.StdEncoding.EncodeToString(token)
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package account
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
// /account/register - register account
|
|
||||||
func Register(username, password string) error {
|
|
||||||
if !isValidUsername(username) {
|
|
||||||
return fmt.Errorf("invalid username")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(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, username, deriveArgon2IDKey([]byte(password), salt), salt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to add account record: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/api/account"
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/api/daily"
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
scheduleStatRefresh()
|
|
||||||
daily.Init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usernameFromTokenHeader(token string) (string, error) {
|
|
||||||
if token == "" {
|
|
||||||
return "", fmt.Errorf("missing token")
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(token)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to decode token: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decoded) != account.TokenSize {
|
|
||||||
return "", fmt.Errorf("invalid token length: got %d, expected %d", len(token), account.TokenSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
username, err := db.FetchUsernameFromToken(decoded)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to validate token: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return username, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func uuidFromTokenHeader(token string) ([]byte, error) {
|
|
||||||
if token == "" {
|
|
||||||
return nil, fmt.Errorf("missing token")
|
|
||||||
}
|
|
||||||
|
|
||||||
decoded, err := base64.StdEncoding.DecodeString(token)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to decode token: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(decoded) != account.TokenSize {
|
|
||||||
return nil, fmt.Errorf("invalid token length: got %d, expected %d", len(token), account.TokenSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid, err := db.FetchUUIDFromToken(decoded)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to validate token: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return uuid, nil
|
|
||||||
}
|
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
|
"github.com/Flashfyre/pokerogue-server/defs"
|
||||||
|
"github.com/go-co-op/gocron"
|
||||||
|
)
|
||||||
|
|
||||||
|
const secondsPerDay = 60 * 60 * 24
|
||||||
|
|
||||||
|
var (
|
||||||
|
dailyRunScheduler = gocron.NewScheduler(time.UTC)
|
||||||
|
dailyRunSecret []byte
|
||||||
|
dailyRunSeed string
|
||||||
|
)
|
||||||
|
|
||||||
|
func ScheduleDailyRunRefresh() {
|
||||||
|
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")
|
||||||
|
if err != nil {
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("failed to read daily seed secret: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newSecret := make([]byte, 32)
|
||||||
|
_, err := rand.Read(newSecret)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to generate daily seed secret: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile("secret.key", newSecret, 0400)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write daily seed secret: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = newSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
dailyRunSecret = secret
|
||||||
|
|
||||||
|
dailyRunSeed = base64.StdEncoding.EncodeToString(deriveDailyRunSeed(time.Now().UTC()))
|
||||||
|
|
||||||
|
err = db.TryAddDailyRun(dailyRunSeed)
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Daily Run Seed: %s", dailyRunSeed)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deriveDailyRunSeed(seedTime time.Time) []byte {
|
||||||
|
day := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(day, uint64(seedTime.Unix()/secondsPerDay))
|
||||||
|
|
||||||
|
hashedSeed := md5.Sum(append(day, dailyRunSecret...))
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package daily
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const secondsPerDay = 60 * 60 * 24
|
|
||||||
|
|
||||||
var (
|
|
||||||
scheduler = cron.New(cron.WithLocation(time.UTC))
|
|
||||||
secret []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
func Init() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
secret, err = os.ReadFile("secret.key")
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("failed to read daily seed secret: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
newSecret := make([]byte, 32)
|
|
||||||
_, err := rand.Read(newSecret)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to generate daily seed secret: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.WriteFile("secret.key", newSecret, 0400)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to write daily seed secret: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
secret = newSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
err = recordNewDaily()
|
|
||||||
if err != nil {
|
|
||||||
log.Print(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Daily Run Seed: %s", Seed())
|
|
||||||
|
|
||||||
scheduler.AddFunc("@daily", func() {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
|
|
||||||
err := recordNewDaily()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("error while recording new daily: %s", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
scheduler.Start()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Seed() string {
|
|
||||||
return base64.StdEncoding.EncodeToString(deriveSeed(time.Now().UTC()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func deriveSeed(seedTime time.Time) []byte {
|
|
||||||
day := make([]byte, 8)
|
|
||||||
binary.BigEndian.PutUint64(day, uint64(seedTime.Unix()/secondsPerDay))
|
|
||||||
|
|
||||||
hashedSeed := md5.Sum(append(day, secret...))
|
|
||||||
|
|
||||||
return hashedSeed[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func recordNewDaily() error {
|
|
||||||
err := db.TryAddDailyRun(Seed())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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,260 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"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/valyala/fasthttp"
|
|
||||||
)
|
|
||||||
|
|
||||||
/*
|
|
||||||
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 HandleAccountInfo(ctx *fasthttp.RequestCtx) {
|
|
||||||
username, err := usernameFromTokenHeader(string(ctx.Request.Header.Peek("Authorization")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uuid, err := uuidFromTokenHeader(string(ctx.Request.Header.Peek("Authorization"))) // lazy
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := account.Info(username, uuid)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(ctx.Response.BodyWriter()).Encode(response)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleAccountRegister(ctx *fasthttp.RequestCtx) {
|
|
||||||
err := account.Register(string(ctx.PostArgs().Peek("username")), string(ctx.PostArgs().Peek("password")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetStatusCode(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleAccountLogin(ctx *fasthttp.RequestCtx) {
|
|
||||||
response, err := account.Login(string(ctx.PostArgs().Peek("username")), string(ctx.PostArgs().Peek("password")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(ctx.Response.BodyWriter()).Encode(response)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleAccountLogout(ctx *fasthttp.RequestCtx) {
|
|
||||||
token, err := base64.StdEncoding.DecodeString(string(ctx.Request.Header.Peek("Authorization")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to decode token: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = account.Logout(token)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetStatusCode(http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleGamePlayerCount(ctx *fasthttp.RequestCtx) {
|
|
||||||
ctx.SetBody([]byte(strconv.Itoa(playerCount)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleGameTitleStats(ctx *fasthttp.RequestCtx) {
|
|
||||||
err := json.NewEncoder(ctx.Response.BodyWriter()).Encode(defs.TitleStats{
|
|
||||||
PlayerCount: playerCount,
|
|
||||||
BattleCount: battleCount,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleGameClassicSessionCount(ctx *fasthttp.RequestCtx) {
|
|
||||||
ctx.SetBody([]byte(strconv.Itoa(classicSessionCount)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleSaveData(ctx *fasthttp.RequestCtx) {
|
|
||||||
uuid, err := uuidFromTokenHeader(string(ctx.Request.Header.Peek("Authorization")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
datatype := -1
|
|
||||||
|
|
||||||
if ctx.QueryArgs().Has("datatype") {
|
|
||||||
datatype, err = strconv.Atoi(string(ctx.QueryArgs().Peek("datatype")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var slot int
|
|
||||||
if ctx.QueryArgs().Has("slot") {
|
|
||||||
slot, err = strconv.Atoi(string(ctx.QueryArgs().Peek("slot")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var save any
|
|
||||||
// /savedata/get and /savedata/delete specify datatype, but don't expect data in body
|
|
||||||
if string(ctx.Path()) != "/api/savedata/get" && string(ctx.Path()) != "/api/savedata/delete" {
|
|
||||||
if datatype == 0 {
|
|
||||||
var system defs.SystemSaveData
|
|
||||||
err = json.Unmarshal(ctx.Request.Body(), &system)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to unmarshal 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 || string(ctx.Path()) == "/api/savedata/clear" {
|
|
||||||
var session defs.SessionSaveData
|
|
||||||
err = json.Unmarshal(ctx.Request.Body(), &session)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to unmarshal request body: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
save = session
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch string(ctx.Path()) {
|
|
||||||
case "/api/savedata/get":
|
|
||||||
save, err = savedata.Get(uuid, datatype, slot)
|
|
||||||
case "/api/savedata/update":
|
|
||||||
err = savedata.Update(uuid, slot, save)
|
|
||||||
case "/api/savedata/delete":
|
|
||||||
err = savedata.Delete(uuid, datatype, slot)
|
|
||||||
case "/api/savedata/clear":
|
|
||||||
s, ok := save.(defs.SessionSaveData)
|
|
||||||
if !ok {
|
|
||||||
httpError(ctx, fmt.Errorf("save data is not type SessionSaveData"), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// doesn't return a save, but it works
|
|
||||||
save, err = savedata.Clear(uuid, slot, daily.Seed(), s)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if save == nil || string(ctx.Path()) == "/api/savedata/update" {
|
|
||||||
ctx.SetStatusCode(http.StatusOK)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(ctx.Response.BodyWriter()).Encode(save)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDailySeed(ctx *fasthttp.RequestCtx) {
|
|
||||||
ctx.Response.SetBody([]byte(daily.Seed()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDailyRankings(ctx *fasthttp.RequestCtx) {
|
|
||||||
uuid, err := uuidFromTokenHeader(string(ctx.Request.Header.Peek("Authorization")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var category int
|
|
||||||
|
|
||||||
if ctx.QueryArgs().Has("category") {
|
|
||||||
category, err = strconv.Atoi(string(ctx.QueryArgs().Peek("category")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
page := 1
|
|
||||||
if ctx.QueryArgs().Has("page") {
|
|
||||||
page, err = strconv.Atoi(string(ctx.QueryArgs().Peek("page")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to convert page: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rankings, err := daily.Rankings(uuid, category, page)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.NewEncoder(ctx.Response.BodyWriter()).Encode(rankings)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to encode response json: %s", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleDailyRankingPageCount(ctx *fasthttp.RequestCtx) {
|
|
||||||
var category int
|
|
||||||
if ctx.QueryArgs().Has("category") {
|
|
||||||
var err error
|
|
||||||
category, err = strconv.Atoi(string(ctx.QueryArgs().Peek("category")))
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, fmt.Errorf("failed to convert category: %s", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
count, err := daily.RankingPageCount(category)
|
|
||||||
if err != nil {
|
|
||||||
httpError(ctx, err, http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.SetBody([]byte(strconv.Itoa(count)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpError(ctx *fasthttp.RequestCtx, err error, code int) {
|
|
||||||
log.Printf("%s: %s\n", ctx.Path(), err)
|
|
||||||
ctx.Error(err.Error(), code)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func httpError(w http.ResponseWriter, r *http.Request, err error, code int) {
|
||||||
|
log.Printf("%s: %s\n", r.URL.Path, err)
|
||||||
|
http.Error(w, err.Error(), code)
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
|
"github.com/go-co-op/gocron"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
statScheduler = gocron.NewScheduler(time.UTC)
|
||||||
|
playerCount int
|
||||||
|
battleCount int
|
||||||
|
classicSessionCount int
|
||||||
|
)
|
||||||
|
|
||||||
|
func ScheduleStatRefresh() {
|
||||||
|
statScheduler.Every(10).Second().Do(updateStats)
|
||||||
|
statScheduler.StartAsync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStats() {
|
||||||
|
var err error
|
||||||
|
playerCount, err = db.FetchPlayerCount()
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
battleCount, err = db.FetchBattleCount()
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
classicSessionCount, err = db.FetchClassicSessionCount()
|
||||||
|
if err != nil {
|
||||||
|
log.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
+285
@@ -0,0 +1,285 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
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) {
|
||||||
|
gob.Register([]interface{}{})
|
||||||
|
gob.Register(map[string]interface{}{})
|
||||||
|
|
||||||
|
if s.Debug {
|
||||||
|
w.Header().Set("Access-Control-Allow-Headers", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Methods", "*")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
|
||||||
|
if r.Method == "OPTIONS" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.URL.Path {
|
||||||
|
// /account
|
||||||
|
case "/account/info":
|
||||||
|
username, err := getUsernameFromRequest(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":
|
||||||
|
var request AccountRegisterRequest
|
||||||
|
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":
|
||||||
|
var request AccountLoginRequest
|
||||||
|
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":
|
||||||
|
token, err := base64.StdEncoding.DecodeString(r.Header.Get("Authorization"))
|
||||||
|
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":
|
||||||
|
w.Write([]byte(strconv.Itoa(playerCount)))
|
||||||
|
case "/game/titlestats":
|
||||||
|
response, err := json.Marshal(&defs.TitleStats{
|
||||||
|
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":
|
||||||
|
w.Write([]byte(strconv.Itoa(classicSessionCount)))
|
||||||
|
|
||||||
|
// /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":
|
||||||
|
save, err = handleSavedataGet(uuid, datatype, slot)
|
||||||
|
case "/savedata/update":
|
||||||
|
err = handleSavedataUpdate(uuid, slot, save)
|
||||||
|
case "/savedata/delete":
|
||||||
|
err = handleSavedataDelete(uuid, datatype, slot)
|
||||||
|
case "/savedata/clear":
|
||||||
|
// doesn't return a save, but it works
|
||||||
|
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":
|
||||||
|
w.Write([]byte(dailyRunSeed))
|
||||||
|
case "/daily/rankings":
|
||||||
|
uuid, err := getUUIDFromRequest(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":
|
||||||
|
var category int
|
||||||
|
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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth
|
||||||
|
|
||||||
|
type GenericAuthRequest struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GenericAuthResponse struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/defs"
|
||||||
|
"github.com/klauspost/compress/zstd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func readSystemSaveData(uuid []byte) (defs.SystemSaveData, error) {
|
||||||
|
var system defs.SystemSaveData
|
||||||
|
|
||||||
|
save, err := os.ReadFile("userdata/" + hex.EncodeToString(uuid) + "/system.pzs")
|
||||||
|
if err != nil {
|
||||||
|
return system, fmt.Errorf("failed to read save file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zstdReader, err := zstd.NewReader(nil)
|
||||||
|
if err != nil {
|
||||||
|
return system, fmt.Errorf("failed to create zstd reader: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed, err := zstdReader.DecodeAll(save, nil)
|
||||||
|
if err != nil {
|
||||||
|
return system, fmt.Errorf("failed to decompress save file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gobDecoderBuf := bytes.NewBuffer(decompressed)
|
||||||
|
|
||||||
|
err = gob.NewDecoder(gobDecoderBuf).Decode(&system)
|
||||||
|
if err != nil {
|
||||||
|
return system, fmt.Errorf("failed to deserialize save: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return system, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSessionSaveData(uuid []byte, slotID int) (defs.SessionSaveData, error) {
|
||||||
|
var session defs.SessionSaveData
|
||||||
|
|
||||||
|
fileName := "session"
|
||||||
|
if slotID != 0 {
|
||||||
|
fileName += strconv.Itoa(slotID)
|
||||||
|
}
|
||||||
|
|
||||||
|
save, err := os.ReadFile(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
||||||
|
if err != nil {
|
||||||
|
return session, fmt.Errorf("failed to read save file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zstdReader, err := zstd.NewReader(nil)
|
||||||
|
if err != nil {
|
||||||
|
return session, fmt.Errorf("failed to create zstd reader: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
decompressed, err := zstdReader.DecodeAll(save, nil)
|
||||||
|
if err != nil {
|
||||||
|
return session, fmt.Errorf("failed to decompress save file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gobDecoderBuf := bytes.NewBuffer(decompressed)
|
||||||
|
|
||||||
|
err = gob.NewDecoder(gobDecoderBuf).Decode(&session)
|
||||||
|
if err != nil {
|
||||||
|
return session, fmt.Errorf("failed to deserialize save: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return session, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateSessionCompleted(session defs.SessionSaveData) bool {
|
||||||
|
switch session.GameMode {
|
||||||
|
case 0:
|
||||||
|
return session.BattleType == 2 && session.WaveIndex == 200
|
||||||
|
case 3:
|
||||||
|
return session.BattleType == 2 && session.WaveIndex == 50
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
+221
@@ -0,0 +1,221 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
|
"github.com/Flashfyre/pokerogue-server/defs"
|
||||||
|
"github.com/klauspost/compress/zstd"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to update account stats: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var gobBuffer bytes.Buffer
|
||||||
|
err = gob.NewEncoder(&gobBuffer).Encode(save)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to serialize save: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zstdWriter, err := zstd.NewWriter(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create zstd writer, %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed := zstdWriter.EncodeAll(gobBuffer.Bytes(), nil)
|
||||||
|
|
||||||
|
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return fmt.Errorf("failed to create userdata folder: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile("userdata/"+hexUUID+"/system.pzs", compressed, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write save file: %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)
|
||||||
|
}
|
||||||
|
|
||||||
|
var gobBuffer bytes.Buffer
|
||||||
|
err = gob.NewEncoder(&gobBuffer).Encode(save)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to serialize save: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
zstdWriter, err := zstd.NewWriter(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create zstd writer, %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
compressed := zstdWriter.EncodeAll(gobBuffer.Bytes(), nil)
|
||||||
|
|
||||||
|
err = os.MkdirAll("userdata/"+hexUUID, 0755)
|
||||||
|
if err != nil && !os.IsExist(err) {
|
||||||
|
return fmt.Errorf(fmt.Sprintf("failed to create userdata folder: %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(fmt.Sprintf("userdata/%s/%s.pzs", hexUUID, fileName), compressed, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write save file: %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) {
|
||||||
|
err := db.UpdateAccountLastActivity(uuid)
|
||||||
|
if err != nil {
|
||||||
|
log.Print("failed to update account last activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
if slot < 0 || slot >= sessionSlotCount {
|
||||||
|
return SavedataClearResponse{}, fmt.Errorf("slot id %d out of range", slot)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCompleted := validateSessionCompleted(save)
|
||||||
|
newCompletion := false
|
||||||
|
|
||||||
|
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 {
|
||||||
|
newCompletion, 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 SavedataClearResponse{}, fmt.Errorf("failed to delete save file: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return SavedataClearResponse{Success: newCompletion}, nil
|
||||||
|
}
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
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,78 +0,0 @@
|
|||||||
package savedata
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/gob"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/klauspost/compress/zstd"
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/defs"
|
|
||||||
)
|
|
||||||
|
|
||||||
func readSystemSaveData(uuid []byte) (defs.SystemSaveData, error) {
|
|
||||||
var system defs.SystemSaveData
|
|
||||||
|
|
||||||
file, err := os.Open("userdata/" + hex.EncodeToString(uuid) + "/system.pzs")
|
|
||||||
if err != nil {
|
|
||||||
return system, fmt.Errorf("failed to open save file for reading: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
zstdDecoder, err := zstd.NewReader(file)
|
|
||||||
if err != nil {
|
|
||||||
return system, fmt.Errorf("failed to create zstd decoder: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer zstdDecoder.Close()
|
|
||||||
|
|
||||||
err = gob.NewDecoder(zstdDecoder).Decode(&system)
|
|
||||||
if err != nil {
|
|
||||||
return system, fmt.Errorf("failed to deserialize save: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return system, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readSessionSaveData(uuid []byte, slotID int) (defs.SessionSaveData, error) {
|
|
||||||
var session defs.SessionSaveData
|
|
||||||
|
|
||||||
fileName := "session"
|
|
||||||
if slotID != 0 {
|
|
||||||
fileName += strconv.Itoa(slotID)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Open(fmt.Sprintf("userdata/%s/%s.pzs", hex.EncodeToString(uuid), fileName))
|
|
||||||
if err != nil {
|
|
||||||
return session, fmt.Errorf("failed to open save file for reading: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
zstdDecoder, err := zstd.NewReader(file)
|
|
||||||
if err != nil {
|
|
||||||
return session, fmt.Errorf("failed to create zstd decoder: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer zstdDecoder.Close()
|
|
||||||
|
|
||||||
err = gob.NewDecoder(zstdDecoder).Decode(&session)
|
|
||||||
if err != nil {
|
|
||||||
return session, fmt.Errorf("failed to deserialize save: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return session, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateSessionCompleted(session defs.SessionSaveData) bool {
|
|
||||||
switch session.GameMode {
|
|
||||||
case 0:
|
|
||||||
return session.BattleType == 2 && session.WaveIndex == 200
|
|
||||||
case 3:
|
|
||||||
return session.BattleType == 2 && session.WaveIndex == 50
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
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 compensationType, amount := range compensations {
|
|
||||||
system.VoucherCounts[strconv.Itoa(compensationType)] += amount
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
if save.GameVersion != "1.0.2" {
|
|
||||||
return fmt.Errorf("client version out of date")
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
|
||||||
"github.com/robfig/cron/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
scheduler = cron.New(cron.WithLocation(time.UTC))
|
|
||||||
playerCount int
|
|
||||||
battleCount int
|
|
||||||
classicSessionCount int
|
|
||||||
)
|
|
||||||
|
|
||||||
func scheduleStatRefresh() {
|
|
||||||
scheduler.AddFunc("@every 10s", func() {
|
|
||||||
err := updateStats()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to update stats: %s", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
scheduler.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateStats() error {
|
|
||||||
var err error
|
|
||||||
playerCount, err = db.FetchPlayerCount()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
battleCount, err = db.FetchBattleCount()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
classicSessionCount, err = db.FetchClassicSessionCount()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+3
-21
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
|
"github.com/Flashfyre/pokerogue-server/defs"
|
||||||
_ "github.com/go-sql-driver/mysql"
|
_ "github.com/go-sql-driver/mysql"
|
||||||
"github.com/pagefaultgames/pokerogue-server/defs"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func AddAccountRecord(uuid []byte, username string, key, salt []byte) error {
|
func AddAccountRecord(uuid []byte, username string, key, salt []byte) error {
|
||||||
@@ -41,8 +41,8 @@ func UpdateAccountLastActivity(uuid []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateAccountStats(uuid []byte, stats defs.GameStats, voucherCounts map[string]int) error {
|
func UpdateAccountStats(uuid []byte, stats defs.GameStats) error {
|
||||||
var columns = []string{"playTime", "battles", "classicSessionsPlayed", "sessionsWon", "highestEndlessWave", "highestLevel", "pokemonSeen", "pokemonDefeated", "pokemonCaught", "pokemonHatched", "eggsPulled", "regularVouchers", "plusVouchers", "premiumVouchers", "goldenVouchers"}
|
var columns = []string{"playTime", "battles", "classicSessionsPlayed", "sessionsWon", "highestEndlessWave", "highestLevel", "pokemonSeen", "pokemonDefeated", "pokemonCaught", "pokemonHatched", "eggsPulled"}
|
||||||
|
|
||||||
var statCols []string
|
var statCols []string
|
||||||
var statValues []interface{}
|
var statValues []interface{}
|
||||||
@@ -64,24 +64,6 @@ func UpdateAccountStats(uuid []byte, stats defs.GameStats, voucherCounts map[str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range voucherCounts {
|
|
||||||
var column string
|
|
||||||
switch k {
|
|
||||||
case "0":
|
|
||||||
column = "regularVouchers"
|
|
||||||
case "1":
|
|
||||||
column = "plusVouchers"
|
|
||||||
case "2":
|
|
||||||
column = "premiumVouchers"
|
|
||||||
case "3":
|
|
||||||
column = "goldenVouchers"
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
statCols = append(statCols, column)
|
|
||||||
statValues = append(statValues, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
var statArgs []interface{}
|
var statArgs []interface{}
|
||||||
statArgs = append(statArgs, uuid)
|
statArgs = append(statArgs, uuid)
|
||||||
for range 2 {
|
for range 2 {
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ package db
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/defs"
|
"github.com/Flashfyre/pokerogue-server/defs"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TryAddDailyRun(seed string) error {
|
func TryAddDailyRun(seed string) error {
|
||||||
|
|||||||
+2
-2
@@ -12,7 +12,7 @@ func FetchPlayerCount() (int, error) {
|
|||||||
|
|
||||||
func FetchBattleCount() (int, error) {
|
func FetchBattleCount() (int, error) {
|
||||||
var battleCount int
|
var battleCount int
|
||||||
err := handle.QueryRow("SELECT COALESCE(SUM(battles), 0) FROM accountStats").Scan(&battleCount)
|
err := handle.QueryRow("SELECT SUM(battles) FROM accountStats").Scan(&battleCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ func FetchBattleCount() (int, error) {
|
|||||||
|
|
||||||
func FetchClassicSessionCount() (int, error) {
|
func FetchClassicSessionCount() (int, error) {
|
||||||
var classicSessionCount int
|
var classicSessionCount int
|
||||||
err := handle.QueryRow("SELECT COALESCE(SUM(classicSessionsPlayed), 0) FROM accountStats").Scan(&classicSessionCount)
|
err := handle.QueryRow("SELECT SUM(classicSessionsPlayed) FROM accountStats").Scan(&classicSessionCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
func TryAddSeedCompletion(uuid []byte, seed string, mode int) (bool, error) {
|
func TryAddSeedCompletion(uuid []byte, seed string, mode int) (bool, error) {
|
||||||
|
if len(seed) < 24 {
|
||||||
|
for range 24 - len(seed) {
|
||||||
|
seed += "0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var count int
|
var count int
|
||||||
err := handle.QueryRow("SELECT COUNT(*) FROM seedCompletions WHERE uuid = ? AND seed = ?", uuid, seed).Scan(&count)
|
err := handle.QueryRow("SELECT COUNT(*) FROM seedCompletions WHERE uuid = ? AND seed = ?", uuid, seed).Scan(&count)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+2
-16
@@ -1,15 +1,12 @@
|
|||||||
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"`
|
||||||
Gender int `json:"gender"`
|
Gender int `json:"gender"`
|
||||||
DexData DexData `json:"dexData"`
|
DexData DexData `json:"dexData"`
|
||||||
StarterData StarterData `json:"starterData"`
|
StarterMoveData StarterMoveData `json:"starterMoveData"`
|
||||||
StarterMoveData StarterMoveData `json:"starterMoveData"` // Legacy
|
StarterEggMoveData StarterEggMoveData `json:"starterEggMoveData"`
|
||||||
StarterEggMoveData StarterEggMoveData `json:"starterEggMoveData"` // Legacy
|
|
||||||
GameStats GameStats `json:"gameStats"`
|
GameStats GameStats `json:"gameStats"`
|
||||||
Unlocks Unlocks `json:"unlocks"`
|
Unlocks Unlocks `json:"unlocks"`
|
||||||
AchvUnlocks AchvUnlocks `json:"achvUnlocks"`
|
AchvUnlocks AchvUnlocks `json:"achvUnlocks"`
|
||||||
@@ -32,17 +29,6 @@ type DexEntry struct {
|
|||||||
Ivs []int `json:"ivs"`
|
Ivs []int `json:"ivs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type StarterData map[int]StarterEntry
|
|
||||||
|
|
||||||
type StarterEntry struct {
|
|
||||||
Moveset interface{} `json:"moveset"`
|
|
||||||
EggMoves int `json:"eggMoves"`
|
|
||||||
CandyCount int `json:"candyCount"`
|
|
||||||
AbilityAttr int `json:"abilityAttr"`
|
|
||||||
PassiveAttr int `json:"passiveAttr"`
|
|
||||||
ValueReduction int `json:"valueReduction"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type StarterMoveData map[int]interface{}
|
type StarterMoveData map[int]interface{}
|
||||||
|
|
||||||
type StarterEggMoveData map[int]int
|
type StarterEggMoveData map[int]int
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
module github.com/pagefaultgames/pokerogue-server
|
module github.com/Flashfyre/pokerogue-server
|
||||||
|
|
||||||
go 1.22
|
go 1.22
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-co-op/gocron v1.37.0
|
||||||
github.com/go-sql-driver/mysql v1.7.1
|
github.com/go-sql-driver/mysql v1.7.1
|
||||||
github.com/klauspost/compress v1.17.6
|
github.com/klauspost/compress v1.17.4
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
golang.org/x/crypto v0.16.0
|
||||||
github.com/valyala/fasthttp v1.52.0
|
|
||||||
golang.org/x/crypto v0.19.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
github.com/google/uuid v1.4.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
golang.org/x/sys v0.15.0 // indirect
|
||||||
)
|
)
|
||||||
@@ -1,16 +1,46 @@
|
|||||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
|
||||||
|
github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
|
||||||
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
|
||||||
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||||
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||||
|
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||||
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
|
||||||
|
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||||
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
+26
-62
@@ -1,24 +1,21 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/pagefaultgames/pokerogue-server/api"
|
"github.com/Flashfyre/pokerogue-server/api"
|
||||||
"github.com/pagefaultgames/pokerogue-server/db"
|
"github.com/Flashfyre/pokerogue-server/db"
|
||||||
"github.com/valyala/fasthttp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var serveStaticContent fasthttp.RequestHandler
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// flag stuff
|
debug := flag.Bool("debug", false, "debug mode")
|
||||||
addr := flag.String("addr", "0.0.0.0:80", "network address for api to listen on")
|
|
||||||
wwwpath := flag.String("wwwpath", "www", "path to static content to serve")
|
proto := flag.String("proto", "tcp", "protocol for api to use (tcp, unix)")
|
||||||
tlscert := flag.String("tlscert", "", "path to tls certificate to use for https")
|
addr := flag.String("addr", "0.0.0.0", "network address for api to listen on")
|
||||||
tlskey := flag.String("tlskey", "", "path to tls private key to use for https")
|
|
||||||
|
|
||||||
dbuser := flag.String("dbuser", "pokerogue", "database username")
|
dbuser := flag.String("dbuser", "pokerogue", "database username")
|
||||||
dbpass := flag.String("dbpass", "", "database password")
|
dbpass := flag.String("dbpass", "", "database password")
|
||||||
@@ -28,63 +25,30 @@ func main() {
|
|||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
// register gob types
|
|
||||||
gob.Register([]interface{}{})
|
|
||||||
gob.Register(map[string]interface{}{})
|
|
||||||
|
|
||||||
// get database connection
|
|
||||||
err := db.Init(*dbuser, *dbpass, *dbproto, *dbaddr, *dbname)
|
err := db.Init(*dbuser, *dbpass, *dbproto, *dbaddr, *dbname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to initialize database: %s", err)
|
log.Fatalf("failed to initialize database: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// start web server
|
if *proto == "unix" {
|
||||||
serveStaticContent = fasthttp.FSHandler(*wwwpath, 0)
|
os.Remove(*addr)
|
||||||
|
|
||||||
api.Init()
|
|
||||||
|
|
||||||
if *tlscert != "" && *tlskey != "" {
|
|
||||||
err = fasthttp.ListenAndServeTLS(*addr, *tlscert, *tlskey, serve)
|
|
||||||
} else {
|
|
||||||
err = fasthttp.ListenAndServe(*addr, serve)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen(*proto, *addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create net listener: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *proto == "unix" {
|
||||||
|
os.Chmod(*addr, 0777)
|
||||||
|
}
|
||||||
|
|
||||||
|
api.ScheduleStatRefresh()
|
||||||
|
api.ScheduleDailyRunRefresh()
|
||||||
|
api.InitDailyRun()
|
||||||
|
|
||||||
|
err = http.Serve(listener, &api.Server{Debug: *debug})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to create http server or server errored: %s", err)
|
log.Fatalf("failed to create http server or server errored: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func serve(ctx *fasthttp.RequestCtx) {
|
|
||||||
if strings.HasPrefix(string(ctx.Path()), "/api") {
|
|
||||||
switch string(ctx.Path()) {
|
|
||||||
case "/api/account/info":
|
|
||||||
api.HandleAccountInfo(ctx)
|
|
||||||
case "/api/account/register":
|
|
||||||
api.HandleAccountRegister(ctx)
|
|
||||||
case "/api/account/login":
|
|
||||||
api.HandleAccountLogin(ctx)
|
|
||||||
case "/api/account/logout":
|
|
||||||
api.HandleAccountLogout(ctx)
|
|
||||||
|
|
||||||
case "/api/game/playercount":
|
|
||||||
api.HandleGamePlayerCount(ctx)
|
|
||||||
case "/api/game/titlestats":
|
|
||||||
api.HandleGameTitleStats(ctx)
|
|
||||||
case "/api/game/classicsessioncount":
|
|
||||||
api.HandleGameClassicSessionCount(ctx)
|
|
||||||
|
|
||||||
case "/api/savedata/get", "/api/savedata/update", "/api/savedata/delete", "/api/savedata/clear":
|
|
||||||
api.HandleSaveData(ctx)
|
|
||||||
|
|
||||||
case "/api/daily/seed":
|
|
||||||
api.HandleDailySeed(ctx)
|
|
||||||
case "/api/daily/rankings":
|
|
||||||
api.HandleDailyRankings(ctx)
|
|
||||||
case "/api/daily/rankingpagecount":
|
|
||||||
api.HandleDailyRankingPageCount(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
serveStaticContent(ctx)
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user